JShell: the Java REPL
Try Java expressions, APIs and regular expressions interactively with JShell, load your own classes into it, and script repeatable checks.
jshell # start the REPL
jshell --class-path target/classes # with your compiled classes
jshell script.jsh # run a script of snippets
jshell> /help # list commandsJShell 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.splitdoes with trailing empty strings, what aDateTimeFormatterpattern 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
| Command | Effect |
|---|---|
/vars, /methods, /types | List what you have declared. |
/list | Show your snippets with their ids. |
/edit name | Edit a snippet in an editor. |
/save file.jsh, /open file.jsh | Save the session, or load a script. |
/reset | Start again with a clean state. |
/env --class-path dir | Add compiled classes or JARs. |
/exit | Leave. |
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
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> /exitWhen 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.
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-28Calling 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 :.
# 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.98A 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.
// 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