JShell
JShell, introduced in Java 9, is an interactive Read-Eval-Print Loop (REPL) bundled directly with the JDK, allowing Java statements, expressions, method, class, and variable declarations to be entered and evaluated one at a time at an interactive prompt, with immediate feedback — without requiring a class wrapper, a main method, explicit compilation, or a build tool, all of which Java had always required for even the smallest snippet of executable code before Java 9. This solved a long-standing friction point for learning, quick experimentation, and API exploration, where languages like Python or Ruby offered an immediate interactive prompt but Java required the full edit-compile-run cycle even to test a single line. This entry covers what problem JShell solves and how it differs from a script, core REPL usage including implicit variable creation and forward references, snippet management commands, and how to feed JShell external classes and files.
Motivation — Java Lacked an Interactive Prompt
// ── Before Java 9 — testing one expression required a full class+build cycle
// Scratch.java
public class Scratch {
public static void main(String[] args) {
System.out.println(String.format("%.2f", 3.14159));
}
}
// $ javac Scratch.java
// $ java Scratch
// 3.14
// Three steps (write file, compile, run) just to check one format string
// ── Java 9+ — JShell evaluates the same expression immediately ─────────
// $ jshell
// jshell> String.format("%.2f", 3.14159)
// $1 ==> "3.14"
//
// No class wrapper, no main method, no separate compile step — the result
// (and JShell's auto-assigned scratch variable $1) appears instantly.Core REPL Behavior — Implicit Variables, Forward References, and Statement Evaluation
// jshell> 2 + 2
// $1 ==> 4
// jshell> $1 * 10 // referring back to a previous scratch result
// $2 ==> 40
// jshell> int x = 10
// x ==> 10
// jshell> int x = 20 // redeclaring "x" simply replaces the earlier one
// x ==> 20
// ── Forward references — calling a not-yet-defined method is accepted ──
// jshell> int square(int n) { return doubleIt(n) * n / 2; } // doubleIt() doesn't exist yet
// | created method square(int), however, it cannot be invoked until method
// | doubleIt(int) is declared
// jshell> int doubleIt(int n) { return n * 2; } // now define it
// | created method doubleIt(int)
// jshell> square(5) // NOW it resolves and runs correctly
// $3 ==> 25
// ── Bare statements run immediately, no main() wrapper needed ──────────
// jshell> for (int i = 0; i < 3; i++) { System.out.println("Hi " + i); }
// Hi 0
// Hi 1
// Hi 2
// jshell> List<String> names = List.of("Ann", "Bo", "Cy")
// names ==> [Ann, Bo, Cy]
// jshell> names.forEach(System.out::println)
// Ann
// Bo
// CySnippet Management Commands and Loading External Code
// ── Listing what's currently defined in the session ─────────────────────
// jshell> /vars
// | int x = 20
// | List<String> names = [Ann, Bo, Cy]
// jshell> /methods
// | int square(int)
// | int doubleIt(int)
// jshell> /list
// 1 : int x = 10;
// 2 : int x = 20;
// 3 : int square(int n) { return doubleIt(n) * n / 2; }
// 4 : int doubleIt(int n) { return n * 2; }
// ── Saving and reloading a session ──────────────────────────────────────
// jshell> /save my-session.jsh
// jshell> /exit
// $ jshell
// jshell> /open my-session.jsh // replays all saved snippets in order
// ── Loading a plain file of statements into the running session ────────
// setup.jsh:
// List<String> users = new ArrayList<>();
// users.add("alice");
// users.add("bob");
// jshell> /open setup.jsh
// jshell> users
// users ==> [alice, bob]
// ── Using external/project classes from the classpath ───────────────────
// $ jshell -cp target/classes:libs/some-library.jar
// jshell> import com.myapp.UserService
// jshell> UserService svc = new UserService()
// jshell> svc.findUser("alice")