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 snippetsPRINTING
- a file containing a number of pre-defined print methodsJAVASE
- 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 lineCTRL-E
- Move the cursor to the end of the current lineALT-F
- Move forward a wordALT-B
- Move backwards a wordCTRL-K
- Cut forwards to the end of lineCTRL-U
- Cut backwards to the first of the lineCTRL-W
- Cut the word before the cursor to the clipboardCTRL-Y
- Paste the last item from the clipboardCTRL-R
- Search backwards through historyCTRL-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.