diff --git a/src/java.base/share/classes/java/io/Console.java b/src/java.base/share/classes/java/io/Console.java
index bbf2e39eb48..2ad5d2c0c1f 100644
--- a/src/java.base/share/classes/java/io/Console.java
+++ b/src/java.base/share/classes/java/io/Console.java
@@ -35,0 +36 @@
+import jdk.internal.javac.PreviewFeature;
@@ -147,0 +149,63 @@ public Reader reader() {
+    /**
+     * Writes a string representation of the specified object to this console's
+     * output stream, terminates the line using {@link System#lineSeparator()}
+     * and then flushes the console.
+     *
+     * <p> The string representation of the specified object is obtained as if
+     * by calling {@link String#valueOf(Object)}.
+     *
+     * @param  obj
+     *         An object whose string representation is to be written,
+     *         may be {@code null}.
+     *
+     * @return  This console
+     *
+     * @since 23
+     */
+    @PreviewFeature(feature = PreviewFeature.Feature.IMPLICIT_CLASSES)
+    public Console println(Object obj) {
+        throw newUnsupportedOperationException();
+    }
+
+    /**
+     * Writes a string representation of the specified object to this console's
+     * output stream and then flushes the console.
+     *
+     * <p> The string representation of the specified object is obtained as if
+     * by calling {@link String#valueOf(Object)}.
+     *
+     * @param  obj
+     *         An object whose string representation is to be written,
+     *         may be {@code null}.
+     *
+     * @return  This console
+     *
+     * @since 23
+     */
+    @PreviewFeature(feature = PreviewFeature.Feature.IMPLICIT_CLASSES)
+    public Console print(Object obj) {
+        throw newUnsupportedOperationException();
+    }
+
+    /**
+     * Writes a prompt as if by calling {@code print}, then reads a single line
+     * of text from this console.
+     *
+     * @param  prompt
+     *         A prompt string, may be {@code null}.
+     *
+     * @throws IOError
+     *         If an I/O error occurs.
+     *
+     * @return  A string containing the line read from the console, not
+     *          including any line-termination characters, or {@code null}
+     *          if an end of stream has been reached without having read
+     *          any characters.
+     *
+     * @since 23
+     */
+    @PreviewFeature(feature = PreviewFeature.Feature.IMPLICIT_CLASSES)
+    public String readln(String prompt) {
+        throw newUnsupportedOperationException();
+    }
+
diff --git a/src/java.base/share/classes/java/io/IO.java b/src/java.base/share/classes/java/io/IO.java
new file mode 100644
index 00000000000..7485f87f03f
--- /dev/null
+++ b/src/java.base/share/classes/java/io/IO.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.io;
+
+import jdk.internal.javac.PreviewFeature;
+
+/**
+ * A collection of static convenience methods that provide access to
+ * {@linkplain System#console() system console} for implicitly declared classes.
+ *
+ * <p> Each of this class' methods throws {@link IOError} if the system console
+ * is {@code null}; otherwise, the effect is as if a similarly-named method
+ * had been called on that console.
+ *
+ * <p> Input and output from methods in this class use the character set of
+ * the system console as specified by {@link Console#charset}.
+ *
+ * @since 23
+ */
+@PreviewFeature(feature = PreviewFeature.Feature.IMPLICIT_CLASSES)
+public final class IO {
+
+    private IO() {
+        throw new Error("no instances");
+    }
+
+    /**
+     * Writes a string representation of the specified object to the system
+     * console, terminates the line and then flushes that console.
+     *
+     * <p> The effect is as if {@link Console#println(Object) println(obj)}
+     * had been called on {@code System.console()}.
+     *
+     * @param obj the object to print, may be {@code null}
+     *
+     * @throws IOError if {@code System.console()} returns {@code null},
+     *                 or if an I/O error occurs
+     */
+    public static void println(Object obj) {
+        con().println(obj);
+    }
+
+    /**
+     * Writes a string representation of the specified object to the system
+     * console and then flushes that console.
+     *
+     * <p> The effect is as if {@link Console#print(Object) print(obj)}
+     * had been called on {@code System.console()}.
+     *
+     * @param obj the object to print, may be {@code null}
+     *
+     * @throws IOError if {@code System.console()} returns {@code null},
+     *                 or if an I/O error occurs
+     */
+    public static void print(Object obj) {
+        con().print(obj);
+    }
+
+    /**
+     * Writes a prompt as if by calling {@code print}, then reads a single line
+     * of text from the system console.
+     *
+     * <p> The effect is as if {@link Console#readln(String) readln(prompt)}
+     * had been called on {@code System.console()}.
+     *
+     * @param prompt the prompt string, may be {@code null}
+     *
+     * @return a string containing the line read from the system console, not
+     * including any line-termination characters. Returns {@code null} if an
+     * end of stream has been reached without having read any characters.
+     *
+     * @throws IOError if {@code System.console()} returns {@code null},
+     *                 or if an I/O error occurs
+     */
+    public static String readln(String prompt) {
+        return con().readln(prompt);
+    }
+
+    private static Console con() {
+        var con = System.console();
+        if (con != null) {
+            return con;
+        } else {
+            throw new IOError(null);
+        }
+    }
+}
diff --git a/src/java.base/share/classes/java/io/ProxyingConsole.java b/src/java.base/share/classes/java/io/ProxyingConsole.java
index 0e8a6e83301..a24ea192447 100644
--- a/src/java.base/share/classes/java/io/ProxyingConsole.java
+++ b/src/java.base/share/classes/java/io/ProxyingConsole.java
@@ -2 +2 @@
- * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
@@ -81,0 +82,36 @@ public Reader reader() {
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Console println(Object obj) {
+        synchronized (writeLock) {
+            delegate.println(obj);
+        }
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Console print(Object obj) {
+        synchronized (writeLock) {
+            delegate.print(obj);
+        }
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws IOError {@inheritDoc}
+     */
+    @Override
+    public String readln(String prompt) {
+        synchronized (writeLock) {
+            synchronized (readLock) {
+                return delegate.readln(prompt);
+            }
+        }
+    }
+
diff --git a/src/java.base/share/classes/jdk/internal/io/JdkConsole.java b/src/java.base/share/classes/jdk/internal/io/JdkConsole.java
index a75941fd2a5..ad93ceb234b 100644
--- a/src/java.base/share/classes/jdk/internal/io/JdkConsole.java
+++ b/src/java.base/share/classes/jdk/internal/io/JdkConsole.java
@@ -2 +2 @@
- * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
@@ -40,0 +41,3 @@ public interface JdkConsole {
+    JdkConsole println(Object obj);
+    JdkConsole print(Object obj);
+    String readln(String prompt);
diff --git a/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java b/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java
index a75ff582085..90d2a4f93f7 100644
--- a/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java
+++ b/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java
@@ -2 +2 @@
- * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
@@ -58,0 +59,33 @@ public Reader reader() {
+    @Override
+    public JdkConsole println(Object obj) {
+        pw.println(obj);
+        // automatic flushing covers println
+        return this;
+    }
+
+    @Override
+    public JdkConsole print(Object obj) {
+        pw.print(obj);
+        pw.flush(); // automatic flushing does not cover print
+        return this;
+    }
+
+    @Override
+    public String readln(String prompt) {
+        String line = null;
+        synchronized (writeLock) {
+            synchronized(readLock) {
+                pw.print(prompt);
+                pw.flush(); // automatic flushing does not cover print
+                try {
+                    char[] ca = readline(false);
+                    if (ca != null)
+                        line = new String(ca);
+                } catch (IOException x) {
+                    throw new IOError(x);
+                }
+            }
+        }
+        return line;
+    }
+
diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java
index 64255b5ab60..d474f08be77 100644
--- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java
+++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java
@@ -85,0 +86,24 @@ public Reader reader() {
+        @Override
+        public JdkConsole println(Object obj) {
+            writer().println(obj);
+            writer().flush();
+            return this;
+        }
+
+        @Override
+        public JdkConsole print(Object obj) {
+            writer().print(obj);
+            writer().flush();
+            return this;
+        }
+
+        @Override
+        public String readln(String prompt) {
+            try {
+                initJLineIfNeeded();
+                return jline.readLine(prompt == null ? "null" : prompt.replace("%", "%%"));
+            } catch (EndOfFileException eofe) {
+                return null;
+            }
+        }
+
diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/impl/ConsoleImpl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/impl/ConsoleImpl.java
index 202f29e3140..bc12728d3aa 100644
--- a/src/jdk.jshell/share/classes/jdk/jshell/execution/impl/ConsoleImpl.java
+++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/impl/ConsoleImpl.java
@@ -2 +2 @@
- * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
@@ -191,0 +192,40 @@ public void close() throws IOException {
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public JdkConsole println(Object obj) {
+            writer().println(obj);
+            writer().flush();
+            return this;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public JdkConsole print(Object obj) {
+            writer().print(obj);
+            writer().flush();
+            return this;
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @throws IOError {@inheritDoc}
+         */
+        @Override
+        public String readln(String prompt) {
+            try {
+                return sendAndReceive(() -> {
+                    remoteInput.write(Task.READ_LINE.ordinal());
+                    char[] chars = (prompt == null ? "null" : prompt).toCharArray();
+                    sendChars(chars, 0, chars.length);
+                    char[] line = readChars();
+                    return new String(line);
+                });
+            } catch (IOException ex) {
+                throw new IOError(ex);
+            }
+        }
+