JShell: the Java REPL

Try Java expressions, APIs and regular expressions interactively with JShell, load your own classes into it, and script repeatable checks.

Syntaxjshell # start the REPL jshell --class-path target/classes # with your compiled classes jshell script.jsh # run a script of snippets jshell> /help # list commands

JShell is the Java read-eval-print loop, included in every JDK since Java 9. Type jshell in a terminal and you get a prompt that runs each line as soon as you press Enter. There is no class, no main and no compile step to wait for.

What it is good for

  • Checking how an API behaves before you write it into a service: what String.split does with trailing empty strings, what a DateTimeFormatter pattern produces, whether a regular expression matches.
  • Working out a stream pipeline step by step.
  • Calling your own compiled classes against real input while you debug.

How it behaves

Each thing you type is a snippet: an expression, a statement, a variable, a method, a class or an import. Semicolons are optional on a single line. An expression you do not assign is stored in a scratch variable such as $2, which you can use later. Declaring a method or variable with a name that already exists replaces the old one, so you can fix a method and try again. Checked exceptions do not need to be caught at the top level; JShell prints the exception instead.

Common packages such as java.util and java.util.stream are imported at startup; /imports lists what is available. Tab completes names and shows method signatures, which makes it a quick way to explore an unfamiliar class.

Commands worth knowing

CommandEffect
/vars, /methods, /typesList what you have declared.
/listShow your snippets with their ids.
/edit nameEdit a snippet in an editor.
/save file.jsh, /open file.jshSave the session, or load a script.
/resetStart again with a clean state.
/env --class-path dirAdd compiled classes or JARs.
/exitLeave.

Using it with a project

Start JShell with your build output on the class path, for example jshell --class-path target/classes after a Maven build, then import your packages and call your code directly. This is useful for checking a parser or a mapper with real data. It is not a replacement for tests: what you try in JShell is gone when you exit, while a unit test runs on every build.

A file of snippets, by convention ending in .jsh, can be run with jshell file.jsh; end it with /exit so it does not stay at the prompt.

Example

Example · text
jshell> int timeoutSeconds = 3
timeoutSeconds ==> 3

jshell> timeoutSeconds * 1000
$2 ==> 3000

jshell> String slug(String title) { return title.strip().toLowerCase().replace(' ', '-'); }
|  created method slug(String)

jshell> slug("  Getting Started ")
$4 ==> "getting-started"

jshell> record User(String name, int age) {}
|  created record User

jshell> List.of(new User("ada", 36), new User("lin", 29))
$6 ==> [User[name=ada, age=36], User[name=lin, age=29]]

jshell> /exit

When to use it

  • Confirm how a DateTimeFormatter pattern or a regular expression behaves before putting it in a request validator.
  • Load a service's compiled classes and run its JSON mapper against a payload copied from a failing request.
  • Keep a small .jsh smoke-check script for poking a local service's endpoints during development.

More examples

Checking an API's edge cases before using it

Thirty seconds in JShell shows that split drops trailing empty strings unless you pass a negative limit, and that plusMonths clamps to the end of the month. Both are the kind of detail that otherwise turns up as a bug in a CSV import or a billing date.

Example · text
jshell> "a,b,,".split(",")
$1 ==> String[2] { "a", "b" }

jshell> "a,b,,".split(",", -1)
$2 ==> String[4] { "a", "b", "", "" }

jshell> "order-123".matches("order-\\d+")
$3 ==> true

jshell> import java.time.*

jshell> LocalDate.of(2026, 1, 31).plusMonths(1)
$5 ==> 2026-02-28

Calling your own compiled classes

The class path makes your packages importable at the prompt, so you can feed a parser or calculator real input while you debug. On Windows the class path separator is ; instead of :.

Example · bash
# Build first, so the class files exist
mvn -q compile

# Start JShell with the compiled classes and the project's dependencies
jshell --class-path target/classes:$(mvn -q dependency:build-classpath -Dmdep.outputFile=/dev/stdout)

# Then, at the prompt:
#   jshell> import com.example.orders.*
#   jshell> new PriceCalculator().total(List.of(new Line("BOOK-1", 2, new BigDecimal("19.99"))))
#   $2 ==> 39.98

A repeatable smoke-check script

A .jsh file is a sequence of snippets and commands, so the same checks you typed by hand can be rerun against a local service. The final /exit stops JShell from staying at the prompt afterwards.

Example · text
// smoke.jsh: run with  jshell smoke.jsh
import java.net.URI
import java.net.http.*

var client = HttpClient.newHttpClient()
var request = HttpRequest.newBuilder(URI.create("http://localhost:8080/health")).build()
var response = client.send(request, HttpResponse.BodyHandlers.ofString())
System.out.println(response.statusCode() + " " + response.body())

/exit

Discussion

  • Be the first to comment on this lesson.