JShell, the Java REPL

 · 9 min
Ahmed Sobah on Unsplash

Many languages contain a REPL, a Read-Evaluate-Print Loop. It evaluates declarations, statements, and expressions as they are entered and immediately shows the results. With Java 9, we finally got one too.

Quickly running some code without starting a full-blown IDE, or creating a scratch project can be immensely helpful. It can be used for prototyping code, trying some new functionality, or running a small part of a bigger project.

Lines starting with # are added for explanation purposes and are not part of the JShell output.


Basic Usage

To start a new JShell session, just type jshell in your favorite terminal / command line:

jshell [options] [load-files]

Snippets

JShell evaluates everything you throw at it: expressions, statements, imports, or definitions. These chunks of Java code are called “snippets”.

# Expressions
jshell> 2 + 3
$1 ==> 5

# Statements
jshell> var a = 2 + 3
a ==> 5

# Imports
jshell> import java.time.*

# Definitions
jshell> boolean greaterZero(int i) {
   ...> return i > 0;
   ...> }
|  created method greaterZero(int)

No semicolons are needed for single-line snippets. If we define a method or class, we still need semicolons for the contents of curly brackets.

Scratch Variables

Any value returned by a snippet is automatically saved into a “scratch variable”, named $<number>. Now the result of the expression is available to use later on.

To list all variables of the active session, type /vars, which will print all scratch and non-scratch variables:

jshell> 2 + 3
$1 ==> 5jshell> String.valueOf($1)
$2 ==> "5"

jshell> String nonScratch = "value"
nonScratch ==> "value"jshell> /vars
|    int $1 = 5
|    String $2 = "5"
|    String nonScratch = "value"

Imports

By default, only a small subset of the JDK is imported automatically. Typing /imports lists all imported packages and classes of the current JShell session:

jshell> /imports
|    import java.io.*
|    import java.math.*
|    import java.net.*
|    import java.nio.file.*
|    import java.util.*
|    import java.util.concurrent.*
|    import java.util.function.*
|    import java.util.prefs.*
|    import java.util.regex.*
|    import java.util.stream.*

We can import other packages and classes just like we would in “normal” Java code:

jshell> import java.time.*

jshell> import static java.util.stream.Collectors.*

Or we can instruct JShell to load a different set of packages on start-up by using a predefined load-file: jshell JAVASE

This will load ~170 different packages from the JDK, instead of just the bare minimum.

Context-based Auto-Imports

No one wants to type out all the imports needed for working with Java code. It’s a good thing JShell has you covered!

To import a type we can press shift-tab i at the end of any type, and JShell tries to find the correct type based on the context:

#                    cursor position
#                          ▼
jshell> var now = LocalDate.now() <shift-tab i>
0: Do nothing
1: import: java.time.LocalDate
Choice: 1

Not always an import-candidate will be found. But most of the time, it will make using JShell easier.

Expressions to Variables

Another handy shortcut is shift-tab v which will convert an expression to a variable creation statement:

jshell> 3 + 3 <shift-tab v>
# New cursor position
#           ▼
jshell> int  = 3 + 3

The variable type will be inferred and inserted automatically, just give it a name, press enter, and you converted your expression to a named variable.


The Power of Tab

The tab completion is really powerful and can be used to complete snippets and commands. Pressing tab multiple times increases the amount of information provided.

jshell> System.out.print <tab>
print(     printf(    println(

jshell> System.out.print

If we found the correct method, we can see all available signatures by pressing tab again after the parenthesis:

jshell> System.out.println(
Signatures:
void PrintStream.println()
void PrintStream.println(boolean x)
void PrintStream.println(char x)
void PrintStream.println(int x)
void PrintStream.println(long x)
void PrintStream.println(float x)
void PrintStream.println(double x)
void PrintStream.println(char[] x)
void PrintStream.println(String x)
void PrintStream.println(Object x)

<press tab again to see documentation>

jshell> System.out.println(

As indicated in the output, pressing tab again (and again and again) will cycle through the documentation of every signature.

Methods & Classes

Methods and classes can be declared “as usual”:

jshell> String concat(String a, String b) {
   ...> return a + b;
   ...> }
|  created method concat(String,String)

jshell> class SimplePojo {
   ...> private String value;
   ...> String getValue() {
   ...> return this.value;
   ...> }
   ...> void setValue(String newValue) {
   ...> this.value = newValue;
   ...> }
   ...> }
|  created class SimplePojo

jshell> var pojo = new SimplePojo()
pojo ==> SimplePojo@67424e82

jshell> pojo.setValue("a value")

jshell> pojo.getValue()
$7 ==> "a value"

Forward Referencing

Thanks to forward referencing, our methods and classes can use variables and other constructs that aren’t even declared yet. JShell warns us about it, though:

jshell> void forwardReference() {
   ...> System.out.println(notYetDeclared);
   ...> }
|  created method forwardReference(), however, it cannot be invoked until variable notYetDeclared is declared

jshell> forwardReference()
|  attempted to call method forwardReference() which cannot be invoked until variable notYetDeclared is declared

jshell> var notYet = "variable is declared"
notYetDeclared ==> "variable is declared"

jshell> forwardReference()
variable is declared

This allows us to set up our environment easier than ensuring everything has to be in place before creating methods etc.

Control Flow

Java control flow statements are supported:

  • if-else
  • ? : Ternary operator
  • for and while-loops
  • switch statements

History

There are multiple ways to work with previous snippets.

Listing

The command /history will display all snippets and commands of the currently active session in plain text.

jshell> 2 + 4
$1 ==> 6

jshell> String test = "42"
test ==> "42"

jshell> /history

2 + 4
String test = "42"

Using /list instead, we get a list of snippets, but not commands, and their corresponding ID.

jshell> 2 + 4
$1 ==> 6

jshell> String test = "42"
test ==> "42"

jshell> /list

   1 : 2 + 4
   2 : String test = "42";

The ID can be used in other commands.

Searching

JShell supports backward and forward searching:

  • ctrl-r - Backward
  • ctrl-s - Backward

Commands

We already learned about some available commands, like /vars and /list.

Many commands have different options. We can check them out by typing /? or /help for a short overview. For a detailed explanation of a command, use/? <command> or /help <command>.

List of Commands

 Command            | Description
--------------------|------------------------------------
 /!                 | Rerun last snippet
 /-<n>              | Rerun the last <n>-th snippet
 /<id>              | Rerun specific snippet
                    |
 /list              | List source of session
 /vars              | List variables
 /types             | List types
 /methods           | List methods
 /imports           | List imports
                    |
 /history           | Plain text history
 /drop <name or id> | Delete source entry
 /edit              | Open all source in editor
 /edit <id>         | Open specific source in editor
 /open <file>       | Open file as source input
 /save <file>       | Save snippets to <file>
                    |
 /reload            | Reset and replay each snippet
 /reset             | Reset JShell
 /env               | View or change the evaluation context
 /set <key> <value> | Configure JShell
 /exit              | Quit JShell

Command Abbreviations

We don’t have to type out the full command, just enough to make it unique:

jshell> /set feedback verbose

VS.

jshell> /se fe v

/s wouldn’t be enough for /set, thanks to /save.


External Code

Java provides a lot of functionality, but being able to use any JAR file in JShell makes it a potent tool for prototyping.

Classpath

We can either specify the classpath on startup:

jshell --class-path [directory or JAR files]

Or we can set it during an active session:

jshell> /env --class-path [directory or JAR files]

Beware, the session will be reset, and everything not in a start-up script will be gone.

Java 9 Modules

Module usage are similar to using classpaths:

jshell --module-path [module path] --add-modules [module]

This can also be set in an active session:

jshell> /env --module-path [module-path] --add-modules [module]

Configuration

With the help of /set, we can configure JShell to our liking.

Basic Configuration

 Command            | Description
--------------------|------------------------------------
 /!                 | Rerun last snippet
 /-<n>              | Rerun the last <n>-th snippet
 /<id>              | Rerun specific snippet
                    |
 /list              | List source of session
 /vars              | List variables
 /types             | List types
 /methods           | List methods
 /imports           | List imports
                    |
 /history           | Plain text history
 /drop <name or id> | Delete source entry
 /edit              | Open all source in editor
 /edit <id>         | Open specific source in editor
 /open <file>       | Open file as source input
 /save <file>       | Save snippets to <file>
                    |
 /reload            | Reset and replay each snippet
 /reset             | Reset JShell
 /env               | View or change the evaluation context
 /set <key> <value> | Configure JShell
 /exit              | Quit JShell

Like other commands, editor has additional options, you can check them out with /help /set editor.

Persistence

By default, all /set commands are only set for the duration of the current session. To set a configuration option permanently, we need to add the argument -retain at the end.

But be aware, not every retained option can be removed with -delete like the editor option. I’ve tried to find out where JShell saves its configuration, but couldn’t find it, even after checking out the source code.

Advanced Configuration

We can configure the output provided by JShell in quite detail. But this would make this article twice or thrice as long. If you’re interested in configuring the output, you should check out the /set mode and /set format options.

JShell Feedback Mode Documentation (Oracle)


Working with Scripts

Start-up Scripts

As mentioned before, JShell loads a default start-up script to prepare the session with useful imports.

We can use our own script at startup to prepare it the way we like it:

jshell --startup [script]

To use more than one start-up script, we need to use the argument --start instead:

jshell --start [script] --start [script]

Or we can use /set start -retain [scripts] instead.

There are 3 built-in start-up scripts available:

  • DEFAULT: Loads a minimum of imports, used if no load-file is specified.
  • JAVASE: Loads ~175 different JDK packages.
  • PRINTING: Loads the same imports as DEFAULT, but also adds multiple print convenience methods.

Startup scripts are always run when the session resets:

  • Initial start-up
  • /reset
  • /reload
  • /env

Loading Scripts

Loading a script into a session is also possible:

jshell [load-file]

Or use /open [load-file] in a session.

Unlike a start-up script, it won’t be reloaded when the session resets.


Resources