The shell or the REPL is a well-known tool in many programming languages. Typically, this is more common in scripting languages such as Python or Node, but more recently it’s been adopted by JVM languages like Clojure and Groovy as well. The upcoming Java 9 release finally brings this shell functionality to the Java language as well, in the form of JShell.
This article will explore what we can do with JShell and how to make the most of it.
Simply put, the REPL is an interactive shell into which we can enter commands and have these immediately executed and the results displayed.
The use of this tool tools can greatly help in trying out new ideas and techniques and in quickly testing code without needing to write an entire test class to run.
At the core, this is all about an immediate feedback loop and can have a significant impact on productivity in that particular language.
It’s worth noting that there already are a few options for REPL-like functionality in the Java ecosystem.
Also, some JVM languages already have their own REPL – for instance, Clojure, Scala, and Groovy. Unfortunately, these are solutions are specific to those languages and of course, can’t handle Java.
So, while we can make good use of shell implementations on the JVM, Clojure, for example, looks very different to Java:
Clojure 1.4.0 user=> (+ 3 3) 6 user=>
There are also occasionally compatibility issues, due to differences in how these interact with Java libraries, which can make them less suitable for certain scenarios.
Next, there’s also the Java BeanShell. This can be a good option, but is a third party-tool that’s not standard, nor is it available by default, as well as misses some features such as being able to save the script out to a file for later use.
Finally, some IDEs also have limited support for this kind of shell functionality. For example, IntelliJ has the Evaluate Expression feature. These are generally of limited use as compared to a full REPL and would need to have the full IDE running to be able to use – which can be a large overhead when we only want to perform a simple test.
And so, given none of these solutions is a clear winner, Java 9 is finally introducing a standard, official REPL for Java – JSell.
JShell is now a standard component of the Java 9 JDK. So, naturally, you’ll need to use Java 9 – available for download from the Oracle site – to test it.
Once installed, JShell can be started by simply executing the jshell command:
$ jshell | Welcome to JShell -- Version 9 | For an introduction type: /help intro ==> 1+1 $1 ==> 2 ==>
Additionally, like any other command, it accepts additional options to control its behavior.
Of most interest here will be the options to load external classes – either from traditional JAR files or from the new Java Module format.
Specifying JAR files to load is done using the standard Java classpath, using either the CLASSPATH environment variable or the –class-path command line option:
$ jshell --class-path junit-4.12.jar | Welcome to JShell -- Version 9 | For an introduction type: /help intro ==> import org.junit.Test ==>
And specifying Java modules is done using the –add-modules and –module-path command line options:
$ jshell --add-modules jdk.incubator.httpclient | Welcome to JShell -- Version 9 | For an introduction type: /help intro ==> import jdk.incubator.http.*; ==>
Other interesting options are to adjust what is called the feedback mode, which is the level of output from any processed commands. That can be done using the –feedback option, with a parameter of verbose, normal, concise or silent:
$ jshell --feedback verbose | Welcome to JShell -- Version 9 | For an introduction type: /help intro ==> 1+1 $1 ==> 2 | created scratch variable $1 : int ==>
There’s also the option to load a script on startup, including some special predefined options. These are specified using the —startup flag, passing in either a filename or one of:
$ jshell --startup PRINTING | Welcome to JShell -- Version 9 | For an introduction type: /help intro ==> printf("%d\n", 1) 1 ==>
At its very simplest, JShell allows us to execute basic expressions and displays their output. These can be as simple as a maths equation – 1+1 – or as complex as anything that can be expressed in Java.
Anything to the right of an assignment operator can be used here to get an immediate output:
==> 1 + 1 $1 ==> 2 ==> "Hello, World".substring(3, 5).toUpperCase() $2 ==> "LO" ==>
Every time we evaluate an expression like this, it assigns the result to a generated variable – for example, the result of 1 + 1 above was assigned to the variable $1. These can be used like any other variable:
==> printf("%d. %s. ", $1, $2) 2. LO.
Every time we evaluate an expression that’s not assigned to anything, JShell automatically generates the next variable name in sequence and uses it; the tool won’t ever reuse these generated variables automatically.
And, once a variable name is generated like this, it will behave exactly the same as any other variable from that point forward. This is a simple but very useful shorthand for when we wish to try something out and want to make sure we’re not going to lose the result and can always go back and access it later.
To keep things simple, JShell also doesn’t need semicolons to end statements, so using them is entirely optional. This can make it difficult to write complex statements across multiple lines, as it will automatically execute the statement eagerly, as soon as it makes sense to do so:
==> "Hello, World" $3 ==> "Hello, World" ==> "Hello, World".substring( ...> 3, 5) $4 ==> "lo" ==>
If we run JShell with the feedback mode set to verbose – then it will include typenames in all variable descriptions. This can be useful if we are trying to determine the types returned by an expression – for example during division with different input types:
==> 1 / 2 $1 ==> 0 | created scratch variable $1 : int ==> 1.0 / 2.0 $2 ==> 0.5 | created scratch variable $2 : double ==> 1.0 / 2 $3 ==> 0.5 | created scratch variable $3 : double ==> 1 / 2.0 $4 ==> 0.5 | created scratch variable $4 : double ==>
Here we have a very quick demo of how the division of two ints returns an int, but if either side of the division is a double then the result will also be a double.
JShell also has full support for defining named variables with almost the full power of the Java language. These are standard variables, and can be reassigned as desired:
==> int i = 1; i ==> 1 ==> i = 2; i ==> 2
Visibility modifiers are available for use, but they will have no effect on the code because everything runs in the same scope:
==> private int j; j ==> 0 ==> public int k; k ==> 0
The static and final modifiers can also be used, but will generate a warning and will simply be ignored. Note that, in this case, the variable will still be declared and usable:
==> final int l = 3; | Warning: | Modifier 'final' not permitted in top-level declarations, ignored | final int l = 3; | ^---^ l ==> 3 ==> static int m = 3; | Warning: | Modifier 'static' not permitted in top-level declarations, ignored | static int m = 3; | ^----^ m ==> 3
This has the slightly odd side effect that a final variable is not final and so can be reassigned within JShell.
==> l = 4; l ==> 4
If we need to, we can redefine variables and they’ll be immediately replaced by the new definition. This will also reset them to their default value, even if the types are compatible:
==> int i = 1; i ==> 1 ==> long i; i ==> 0 ==>
This can be used with generated variable names as well, as these adhere to the exact same behavior:
==> 1; $3 ==> 1 ==> long $3; $3 ==> 0 ==>
Next, let’s discuss one of the more advanced features we can use in the new Java shell.
JShell has good support for top-level methods – these can be defined and then accessed within the same session.
We can define methods in the same way as we would any normal static method, with the note that the static keyword is unnecessary:
==> int add(int a, int b){ ...> return a + b; ...> } | created method add(int,int) ==> add(1, 2); $8 ==> 3 ==>
And, if we need to, we can replace an existing method with a new method by simply redefining it to have the same signature:
==> int add(int a, int b){ ...> return a - b; ...> } | modified method add(int,int) ==> add(1, 2); $10 ==> -1 ==>
And so, this can allow us to try things out in a session, and keep working until we’re happy with the result. We do not need to restart the session every time we change what we want to do, which of course makes the process very efficient.
Moving on from methods, let’s now look at defining a full class.
It’s actually possible to declare an entire class within the JShell session, and then use it as needed, later in the session. This uses all of the standard Java syntax for class declarations, and can do anything that a normal Java class can do:
==> class Example{ ...> private String name; ...> public String getName(){ ...> return this.name; ...> } ...> public void setName(String name){ ...> this.name = name; ...> } ...> } | created class Example ==> Example e = new Example(); e ==> Example@ff5b51f ==> e.setName("Test"); ==> e.getName(); $4 ==> "Test" ==>
It’s also possible to redefine classes that we’ve already defined, in which case any variables defined of that type will get automatically nulled out for safety reasons:
==> class Example {} | replaced class Example | update replaced variable e, reset to null ==> e e ==> null ==> e.getName(); | Error: | cannot find symbol | symbol: method getName() | e.getName(); | ^-------^ ==>
JShell allows us to do almost anything that the full Java language can do. This means that accessing other classes available to the JVM is an essential requirement.
In order to access a class, we simply refer to it by its fully qualified class name:
==> java.time.Instant.now(); $1 ==> 2017-08-27T09:08:14.399250Z
Anything on the classpath can be accessed this way, as is standard for Java code.
Of course, this can get very noisy, so we do have access to the standard import keyword, usable in the exact same ways as in the Java language:
==> import java.time.Instant; ==> Instant.now(); $3 ==> 2017-08-27T09:10:09.482589Z ==> import static java.time.Instant.now; ==> now(); $5 ==> 2017-08-27T09:10:34.494620Z ==>
As a result, if we try to use a name that isn’t visible – because we got it wrong, or because it is not imported – then JShell will give a helpful error to tell us exactly where we went wrong:
==> java.time.Instant.then(); | Error: | cannot find symbol | symbol: method then() | java.time.Instant.then(); | ^--------------------^ ==>
Finally, if we wish to see the imports that are already available we can use the JShell command /imports:
==> /imports | import java.io.* | import java.util.stream.* | import java.time.Instant | import static java.time.Instant.now ==>
Many of those are available if we use the DEFAULT startup script, as described above. Alternatively, if we use the JAVASE startup script then we get access to significantly more – 173 wildcard package imports.
We’ve briefly touched on the concept of a JShell command that’s not actually part of the Java language. These commands are used to control the JShell session itself, and not to affect the code that we are testing.
These always start with a “/” character to distinguish them from the rest of the commands in the session.
The full name of the command doesn’t need to be entered – a shorthand is enough as long as it’s going to be unique.
For example, /i is enough to represent /imports.
If the command is not unique, then we get a helpful error listing what we could have meant:
==> /e | Command: '/e' is ambiguous: /edit, /exit, /env | Type /help for help. ==>
The full list of commands is available by executing /help. This will give a description of what each command does as well, as would be expected.
In a similar vein to the /imports command, we also have access to /vars to list all of our variables, /methods to list any declared methods and /types to list all of our types.
==> java.time.Instant.now(); $1 ==> 2017-08-27T09:20:46.882828Z ==> /vars | java.time.Instant $1 = 2017-08-27T09:20:46.882828Z ==>
We can also list the definitions that we have created inside the session; to see what we have done, we can make use of the /list command. This can list a specific definition or show them all:
==> java.time.Instant.now(); $1 ==> 2017-08-27T09:47:45.608631Z ==> java.time.Instant.now(); $2 ==> 2017-08-27T09:48:01.074494Z ==> /list 1 : java.time.Instant.now(); 2 : java.time.Instant.now(); ==> /list $1 1 : java.time.Instant.now(); ==>
Finally, we also have the ability to save all of the logic we’ve written in a session out to a file – using the /save command – or to load commands from a file – using the /open command:
==> /save now.java ==> /exit $ cat now.java java.time.Instant.now(); java.time.Instant.now();
Anytime we run a command in JShell that ends up in an Exception reaching the top of the stack, this is automatically handled by displaying the stack trace, including all line numbers, similar to how Java typically handles exceptions.
This can be quite helpful for easily diagnosing problems:
==> String test(String input){ ...> String upper = input.toUpperCase(); ...> return upper.substring(10); ...> } | created method test(String) ==> test("Hello"); | java.lang.StringIndexOutOfBoundsException thrown: String index out of range: -5 | at String.substring (String.java:1852) | at test (#1:3) | at (#2:1) ==>
This tells us an exception was thrown on line 3 of the test() method, which itself was command 1 of the script. We can then see what this was:
==> /list test 1 : String test(String input){ String upper = input.toUpperCase(); return upper.substring(10); } ==>
Therefore, we can immediately see what the offending statement was. Unfortunately, the lack of line numbers means that this is easiest when we have short functions to work with, which is, of course, good practice anyway.
Most modern languages and tools can be interacted with via a shell. Finally, starting with Java 9, so can the Java language itself – a feature that was missing for many years.
JShell is a fantastic tool that has a wide variety of uses. Naturally, the tool is most useful for quickly testing out some code to see how it works without needing to write an entire class, compile it and run it first.
This can greatly improve productivity when developing our code, and shorten the feedback cycle on testing things out.
Stackify Retrace is a great tool to help you continuously improve your Java applications and Stackify Prefix helps you write better code. Try both for free.
If you would like to be a guest contributor to the Stackify blog please reach out to [email protected]