BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles JShell: A Comprehensive Guide to the Java REPL

JShell: A Comprehensive Guide to the Java REPL

Bookmarks

Key Takeaways

  • The Java Shell or JShell is an official Read-Evaluate-Print-Loop, or REPL as it's commonly called, introduced with Java 9. 
  • JShell provides an interactive shell for quickly prototyping, debugging, and learning Java and Java APIs, all without the need for a public static void main or the need to compile your code before executing it. 
  • JShell just got a whole lot less verbose (and more practical) with the advent of the 'var' keyword in Java 10
  • In this article, we’ll take a comprehensive look at JShell, understanding all its commands, its uses, and how to use it most effectively. 
  • JShell is extremely good at providing immediate feedback. It might not seem like much but all those little things (like compiling or running unit tests in my IDE) really do add up over time.

What is JShell?

The Java Shell or JShell is an official Read-Evaluate-Print-Loop, or REPL as it's commonly called, introduced with Java 9. It provides an interactive shell for quickly prototyping, debugging, and learning Java and Java APIs, all without the need for a public static void main or the need to compile your code before executing it. And as an added plus, using JShell just got a whole lot less verbose (and more practical) with the advent of the var keyword in Java 10!

Getting Started

Note: We'll be using Java 10 in this guide to take advantage of the var keyword, so make sure you have at least Java 10 installed to follow along throughout this guided tour.

JShell is easily started by typing jshell at the command-line. You'll be presented with a welcome message and shell waiting for you to input commands or any valid Java expression.

$ jshell
|  Welcome to JShell -- Version 10.0.2
|  For an introduction type: /help intro

Let's execute our first command. At the shell prompt, type var greeting = "hello" and press <enter>. You should see output like below

jshell> var greeting = "hello"
greeting ==> "hello"

You'll notice that it echos the value of greeting, confirming that the value is now equal to hello. You may have also noticed that you didn't need to end the expression with a semicolon. A small but nice feature!

In order to complete our greeting, we need an audience. Type var audience = and press <enter>. This time, JShell recognizes that you're not finished with your expression and allows you to continue on the next line. Finish your expression by typing "world" followed by <enter>. Just like before, JShell echoes the value confirming that it's been set.

jshell> var audience =
  ...> "world"
audience ==> "world"

Tab Completion

One of the first things you'll notice is the nicely integrated tab completion.

Let's concatenate our greeting and audience Strings into a new variable called saying. Start by typing var saying = gr and then press <tab>. You'll see that the greeting variable is automatically completed. Do the same with the audience variable and press enter to see the result of the concatenation.

jshell> var saying = gr<tab> + aud<tab>
saying ==> "helloworld"

Tab completion works just as you'd expect, automatically completing values that are unique or providing potential values when it's ambiguous. It works on any previously typed expressions, objects, and methods. Note that it does not work on built-in keywords.

If you wanted to make the saying variable all uppercase but you couldn't remember the exact name of the method, you could simply type saying.to and then press tab to see all valid possible methods that start with to

jshell> saying.to<tab>
toCharArray()   toLowerCase( toString()      toUpperCase(

Methods with potential arguments are shown with an open parenthesis whereas methods that take no arguments are shown with closed parenthesis.

Errors

If you happen to make a a mistake or enter an invalid expression, method, or command, JShell provides you with immediate feedback by displaying an error and underlining the problem.

jshell> saying.subString(0,1)
|  Error:
|  cannot find symbol
|    symbol:   method subString(int,int)
|  saying.subString(0,1)
|  ^--------------^

Method Signatures

Let's finish calling the toUpperCase( method but hold off on adding any additional arguments or ending it with a parenthesis. Instead, press <tab> an additional time. This time, you'll see all the available method signatures for the toUpperCase method; one with a Locale argument and one without any arguments.

jshell> saying.toUpperCase(
Signatures:
String String.toUpperCase(Locale locale)
String String.toUpperCase()

<press tab again to see documentation>

Documentation (JavaDoc)

If you press <tab> for a third time, you'll see the JavaDoc documentation for the toUpperCase(Locale) method

jshell> saying.toUpperCase(
String String.toUpperCase(Locale locale)
Converts all of the characters in this String to upper case ... (shortened for brevity)

Continue pressing <tab> to cycle through all of the available method signatures and their associated documentation.

Imports

Instead of just hello world, let's expand our example to other audiences like the Universe or Galaxy. Start by creating a List called audiences with three different audiences: world, universe and galaxy. You can do this with a single snippet using the List's constructor and the static factory method List.of introduced in Java 9.

jshell> var audiences = new ArrayList<>(List.of("world", "universe", "galaxy"))
audiences ==> [world, universe, galaxy]

Notice that you didn't have to refer to ArrayList using it's Fully Qualified Class Name (FQCN), nor did you have to import the java.util package! That's because, by default, JShell automatically starts with a number of predefined imports to save you the hassle importing commonly used packages or having to type the FQCN.

Below is a list of the default imports

  • java.io.*
  • java.math.*
  • java.net.*
  • java.nio.file.*
  • java.util.*
  • java.util.concurrent.*
  • java.util.function.*
  • java.util.prefs.*
  • java.util.regex.*
  • java.util.stream.*

As you'd expect, you can also define your own imports if needed, by typing import <pkg_name> where <pkg_name> is a valid package on the classpath.

Methods

Now let's define a method called getRandomAudience to pick one of our audiences at random. The method will take a List<String> of audiences and return a random audience from the list.

You can start defining your method right at the command line, as if you were defining a method as part of a class, however, there's no need to define a class!

jshell> public String getRandomAudience(List<String> audiences) {
  ...> return audiences.get(new Random().nextInt(audiences.size()));
  ...> }
|  created method getRandomAudience(List<String>)

If everything goes right, JShell will echo that it successfully created the method for you and it's available for use.

Let's try out the method by calling it and passing our list of audiences. Call it a few times to ensure you get different results each time.

jshell> getRandomAudience(audiences)
$7 ==> "world"

jshell> getRandomAudience(audiences)
$8 ==> "universe"

jshell> getRandomAudience(audiences)
$9 ==> "galaxy"

One interesting thing to note is that in the method body, you can reference any previously defined variables as well as yet-to-be-defined variables (more on this shortly).

Let's create another version of the getRandomAudience method that takes no arguments and directly uses our audiences list within the body of the method.

jshell> public String getRandomAudience() {
  ...> return audiences.get(new Random().nextInt(audiences.size()));
  ...> }
|  created method getRandomAudience()

Again, let's try it out a few times

jshell> getRandomAudience()
$10 ==> "galaxy"

jshell> getRandomAudience()
$11 ==> "world"

jshell> getRandomAudience()
$12 ==> "galaxy"

As I mentioned before, methods can also utilize yet-to-be-defined variables. Let's define a new method called getSeparator that returns a value we can use to separate words. However, this time we'll reference an undefined variable called wordSeparator.

jshell> public String getSeparator() {
  ...> return wordSeparator;
  ...> }
|  created method getSeparator(), however, it cannot be invoked until variable wordSeparator is declared

Notice that JShell creates our getSeparator method but tells us that it can't be used until we declare or define the wordSeparator variable. Any attempts to call it will result in a similar error message.

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

Simply define the wordSeparator variable as a space and try calling it again.

jshell> var wordSeparator = " "
wordSeparator ==> " "

jshell> getSeparator()
$13 ==> " "

One important thing to note is that you cannot create "top-level" static methods. If you do so, you'll receive a warning message telling you that the static keyword was ignored.

jshell> public static String foobar(String arg) {
  ...> return arg;
  ...> }
|  Warning:
|  Modifier 'static'  not permitted in top-level declarations, ignored
|  public static void foobar(String arg) {
|  ^-----------^

Scratch Variables

In addition to variables which you explicitly declare and define, JShell automatically creates variables for any unassigned expressions. You may have already noticed these variables, called scratch variables, when calling the getSeparator and getRandomAudience methods from the previous section.

Scratch variables follow a common pattern, starting with a dollar sign, and an incrementally increasing number. You can refer to them just as you would any other variable. For instance, let's call the getRandomAudience method again and utilize the result as an argument to System.out.println

jshell> getRandomAudience()
$14 ==> "galaxy"

jshell> System.out.println($14)
galaxy

Classes

You create classes in JShell just like create methods, typing them, line-by-line until the class is complete. JShell then notifies you that it created the class.

jshell> public class Foo {
  ...> private String bar;
  ...> public String getBar() {
  ...> return this.bar;
  ...> }
  ...> }
|  created class Foo

Creating classes (and methods) in JShell can be painful. There's no formatting and making an error can be frustrating since you won't know you've made an error until you complete the class. For a better way to create classes, check out the /open command in the section on JShell Commands.

External Libraries

By now, having learned the basics of JShell, you're likely wondering how you can use JShell with external libraries (jars) such as an internal company library or a common library like Apache Commons. Luckily, it's quite easy to do. You simply start JShell with a --class-path argument. The argument utilizes the standard classpath format with separators.

$ jshell --class-path /path/to/foo.jar

JShell Commands

Up to this point, we've only utilized Java expressions, but JShell also comes with a number of built in commands. Let's switch gears and explore the available commands in JShell.

To see the list of all possible commands, type /help at the prompt. Note that tab completion also works on commands.

jshell> /help
|  Type a Java language expression, statement, or declaration.
|  Or type one of the following commands:
|  /list [<name or id>|-all|-start]
|       list the source you have typed
|  /edit <name or id>
|       edit a source entry
|  /drop <name or id>
|       delete a source entry
(shortened for brevity)

If you want to learn more about a particular command you can type /help <command>, replacing <command> with the name of the command.


jshell> /help list
|
|                                   /list
|                                   =====
|
|  Show the snippets, prefaced with their snippet IDs.

Let's take a look at some of the most useful commands

The List Command

The /list command outputs all of the previously typed snippets, with a unique identifier called the snippet ID.

jshell> /list
  1 : var greeting = "hello";
  2 : var audience = "world";
  3 : var saying = greeting + audience;
  4 : saying.toUpperCase()

By default, the output does not contain any snippets which resulted in an error. Only valid statements or expressions are shown.

To see all previously typed code, including errors, you can pass the -all argument to the /list command.

s1 : import java.io.*;
s2 : import java.math.*;
s3 : import java.net.*;
(shortened for brevity)
s10 : import java.util.stream.*;
1 : var greeting = "hello";
2 : var audience = "world";
3 : var saying = greeting + audience;
4 : saying.toUpperCase()
e1 : var thisIsAnError

The output will contain any startup code (more on this later) as well as any valid or invalid snippets. JShell concatenates a prefix to each snippet ID depending on the type of snippet it is. Here's a quick way to determine the meaning of each:

  • s: Snippet IDs beginning with an s are startup code.
  • e: Snippet IDs beginning with an e are snippets which resulted in an error.
  • Snippet IDs without a prefix are valid snippets.

The Vars, Methods, Types, Imports, and Reset Commands

JShell comes with a number of commands that help you examine the current state, or context, of your shell. They're all appropriately named and straightforward but we'll include them here to be thorough.

You use the /vars to examine all declared variables and their values.

jshell> /vars
|    String greeting = "hello"
|    String audience = "world"
|    String saying = "helloworld"

You use the /methods command to list all declared methods and their signatures.

jshell> /methods
|    String getRandomAudience(List<String>)
|    String getRandomAudience()

You use the /types command to list all type declarations.

jshell> /types
|    class Foo

You use the /imports command to list all of the currently declared imports.

jshell> /imports
|    import java.io.*
|    import java.math.*
|    import java.net.*
(shortened for brevity)

And lastly, you use the /reset command to reset and clear all state including variables, methods, and types.

jshell> /reset
|  Resetting state.

jshell> /vars
(no variables exist after reset)

The Edit Command

/edit is utilized to edit previously typed snippets. The edit command works on all types of snippets including valid, invalid, and startup snippets. It's particularly useful for editing multi-line snippets which resulted in an error, preventing you from having to re-type everything.

Earlier in this article, when the greeting and audience variables were concatenated into the saying variable, a space between "hello" and "world" was missed. You can fix that by typing /edit and then the snippet ID to edit. The JShell Edit Pad will pop up and allow you to make any needed modifications. You can also use the name of the variable instead of the snippet id.

jshell> /edit 3
(... new JShell Edit Pad window opens ...)

jshell> /edit saying
(... new JShell Edit Pad window opens ...)

Once you're finished with your edits, you can click on the Accept button and JShell will re-evaluate the edited snippet. If the re-evaluated snippet doesn't contain any errors, your edited snippet will be assigned a new snippet ID.

You can also edit multiple snippets at once by passing a range or multiple IDs to the /edit command.

jshell> /edit 1-4
(... new JShell Edit Pad window opens with snippets 1 through 4 ...)

jshell> /edit 1 3-4
(... new JShell Edit Pad window opens with snippets 1 and 3 through 4 ...)

The Drop Command

/drop is utilized to delete any previous snippet.

Instead of editing a line, you can also choose to completely delete it using the /drop command. It works in the same way the edit command does where you can use a snippet ID, a variable, a range, or a combination of the former as arguments.

jshell> /drop 3
|  dropped variable $3

jshell> /drop saying
|  dropped variable saying

jshell> /drop 3-4
|  dropped variable saying
|  dropped variable $4

The Save Command

/save allows you to save the output of previously typed snippets to a file.

In addition to the filename to save the output to, the /save command also takes additional arguments to indicate which snippets IDs to save. It utilizes the same form as the /edit and /drop commands and comes before the filename argument.

If you don't include any snippet IDs, all previously typed snippets are saved.

jshell> /save output.txt

jshell> /save 3-4 output.txt

The combination of the /save and /open command (below) can be useful for saving the current session and restoring it at a later time. To save your current session, including all errors, call the /save command with the -all argument.

jshell> /save -all my_jshell_session.txt

The Open Command

The /open command opens any previously saved output and re-evaluates it (including errors!)

jshell> /open my_jshell_session.txt

JShell also comes with a few pre-defined "file names" that you can use for convenience.

  • DEFAULT - a file containing the default import snippets
  • PRINTING - a file containing a number of pre-defined print methods
  • JAVASE - a file containing imports for all of the Java SE packages ()

For instance, if you don't want to have to use System.out.println every time to print something, you can open the PRINTING file which defines a number of shortcut methods, one of which is called print.

jshell> /open PRINTING

jshell> print("hello")
hello

Common and Effective Uses

In order to get the most out of JShell, you should understand some of its common and most effective uses.

JShell is an extremely useful tool for

  • Learning and improving your knowledge of the Java Language
  • Exploring or discovering new APIs both inside and outside of the JDK
  • Quickly prototyping an idea or concept

Using JShell for Learning

We all have areas of Java that we could improve upon. Whether it be Generics or the multi-threading, JShell can be a very effective tool for learning.

What makes JShell such a great tool for learning is that it provides you with an constant feedback loop. You enter a command and it tells you the result. It's as simple as that. And although it's simple, it's effective. It allows you to "move fast and break things", as the saying goes.

Using JShell for Discovery and Exploration

The Java Language is continually evolving and adding new APIs (at an even faster rate than has been witnessed in the past).

For instance, consider the Streams API introduced in Java 8. This was a significant addition to the JDK. There's a LOT to explore. But by no means was the Streams API complete in Java 8. The Streams API is an API that's continually evolving and both Java 9 and Java 10 have added new features and functionality.

Consider using JShell next time you want to explore new features in Java.

Quickly Prototyping with JShell

We've all had situations where we needed to prototype an idea. In those cases, you usually find yourself creating a new test project, writing a JUnit test, or writing a simple Java class with a main method. It's a bit of a ceremony and in reality, it's a bit cumbersome!

JShell can be a very effective tool for testing out new ideas. Instead of writing a unit test or a simple Java class with a main method, you can utilize JShell, either directly at the command-line or via the /open command and a pre-written file. With JShell, some of the things you don't need to do are:

  • Compile your code
  • Name classes and files the same
  • Have multiple source files or nested / inner-classes

And, in the end, all of this equates to faster "idea translation".

Tips for Using JShell

Command-line Tips

JShell utilizes JLine2 to power the command-line. It's a Java equivalent of GNU ReadLine that allows you to edit or navigate commands typed on your command-line. All modern shells such as Bash use it (it's also why you can't type CTRL-V to paste into a shell). That means that JShell has some pretty powerful "shortcuts".

Here are some of the commonly used ones:

  • CTRL-A - Move the cursor to the start of the current line
  • CTRL-E - Move the cursor to the end of the current line
  • ALT-F - Move forward a word
  • ALT-B - Move backwards a word
  • CTRL-K - Cut forwards to the end of line
  • CTRL-U - Cut backwards to the first of the line
  • CTRL-W - Cut the word before the cursor to the clipboard
  • CTRL-Y - Paste the last item from the clipboard
  • CTRL-R - Search backwards through history
  • CTRL-S - Search forwards through history

Classpath Tips

When you're loading external libraries, having to type out the full classpath can be burdensome. You can instead change to the current directory of where all your external libraries are located and start jshell from that directory, utilizing an asterisk (surrounded by quotes) to include all jars. This works on all OSes.

$ jshell --class-path "*"

The same command also works with paths. This also works on all OSes.

$ jshell --class-path "libs/*"

Another nice tip: if you've already typed a number of commands but forgot to set the classpath when you started JShell, you can also utilize the /env command to set the classpath.

jshell> /env --class-path foo.jar
|  Setting new options and restoring state.

Time-saving Tips

You can save yourself a lot of time by maintaining a dedicated directory of commonly used libraries, commands, or snippets for JShell.

For starters, you can fork my example repo at Github

In that repo, you'll find several folders:

  • imports
  • libs
  • startups
  • utils

Let's take a look at each.

Imports

This directory contains predefined, commonly used imports.

You'll find as you use JShell more and more that it becomes quite painful to have to retype a bunch of import statements when you want to use or experiment with a particular external library.

Instead, you can save all the necessary import statements into a file and then utilize the /open command to bring them in.

The level of granularity at which you define your import files is up to you. You can choose to define them on a per-library basis (e.g. guava-imports) or a per project basis (e.g. my-project-imports), or whatever works best for you.

jshell> /open imports/guava-imports

jshell> /imports
(shortened for brevity)
|    import java.util.stream.*
|    import com.google.common.collect.*

Libs

This directory is pretty self-explanatory and contains any and all external libraries that you might use with JShell. You can choose to organize your libraries in whatever fashion makes the most sense, whether it's all in one directory or on a per-project basis.

Whatever your organization strategy is, having all your external libraries organized in an easily loadable way will save you time in the end, as we saw in the Classpath Tips section.

Startups

You can utilize this directory to store any startup or initialization code. This is a feature that's directly supported by JShell with the --startup parameter.

$ jshell --startup startups/custom-startup

#####################
Loaded Custom Startup
#####################

|  Welcome to JShell -- Version 10.0.2
|  For an introduction type: /help intro

jshell>

These files are similar, in nature, to the type of files located in the imports directory but they go beyond just imports. These files are meant to contain any necessary commands, imports, snippets, methods, classes, etc needed to initialize your JShell environment.

If you're familiar with Bash, startup files are a lot like your .bash_profile file.

Utils

We all know how verbose Java can be. This directory, as it's aptly named, is meant to contain any utility or "shortcut code", that will make your time spent with JShell more enjoyable. The type of files you would store here are very similar to the special PRINTING file that comes with JShell, which defines a number of shortcut methods for printing text.

For example, if you're working with large numbers a lot, you'll quickly get tired of having to type new BigInteger for every number you want to add, multiple, subtract, etc. You can, instead, create a util file that contains helper or shortcut methods to reduce the verbosity.

jshell> /open big-integer-utils



jshell> var result = add(bi("123456789987654321"),bi("111111111111111111"))
result ==> 234567901098765432

My Journey with JShell

Admittedly, when I first heard about JShell, I didn't think much of it. I had already used REPLs from other languages and thought of it more as a "toy" than a tool. However, the more I played with it and used it, the more I realized its utility and how it could be useful to me.

For me, I find JShell most helpful for learning new language features, improving my understanding of existing features, debugging code, and experimenting with new libraries. One thing I've learned throughout my career in development, is that anything I can do to shorten my feedback loop, the better. It's how I operate and learn best. And I've found that JShell is extremely good at shortening that feedback loop. It might not seem like much but all those little things (like compiling or running unit tests in my IDE) really do add up over time.

I hope you'll discover the utility of JShell and enjoy it as much as I do!

I would love to hear your comments, thoughts, or experiences with JShell. Please do reach out!

About the Author

Dustin Schultz is a Pluralsight Author, a Principal Software Engineer, and a technology evangelist at heart. He has a true passion for software engineering with over 15 years of experience developing enterprise applications for corporations and startups. He holds a Bachelors and a Masters degree in Computer Science and loves learning. You can learn more about him on his blog.

Rate this Article

Adoption
Style

BT