Creating a Java REPL Playground in Docker

Christian Emmer
Christian Emmer
Jul 3, 2022 · 3 min read
Creating a Java REPL Playground in Docker

It's helpful to have local throwaway environments for testing code snippets, and creating one for Java is a snap with Docker.

REPL is an acronym for "read-eval-print loop ," a type of interactive shell where users get fast feedback from commands executing one a time.

Java can have an involved and somewhat invasive process to install the JDK, and then there's even more work on top of that to set up an IDE with some decent build tools. Sometimes all you want is some quick feedback on Java syntax and behavior without all the setup.

The JShell "interpreter"

From the JShell documentation:

The Java Shell tool (JShell) is an interactive tool for learning the Java programming language and prototyping Java code. JShell is a Read-Evaluate-Print Loop (REPL), which evaluates declarations, statements, and expressions as they are entered and immediately shows the results. The tool is run from the command line.

In short, it lets us run and test code like we might with the Python interpreter .

If you have the JDK installed then you might already have the jshell binary available to you, but maybe you're interested in testing some existing code in a different JDK version, or trying new Java syntax in newer versions.

Using Docker

The OpenJDK image for Docker is an easy way to gain a JShell session:

$ docker run --interactive --tty openjdk:latest jshell
Jul 03, 2022 3:53:40 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
|  Welcome to JShell -- Version 18.0.1.1
|  For an introduction type: /help intro

jshell>

My recommendation is to change the openjdk tag from latest to whatever major version you want, such as openjdk:11.

Because every TUI and interactive shell needs a unique exit command, the command in JShell is /exit. When you exit the JShell session it will stop the Docker container.

To double-check what JDK version is being used, you can run:

jshell> System.getProperty("java.version");
$1 ==> "18.0.1.1"

Working with old JDK versions

JShell is included with JDK 9 (2017) and onward. For a similar experience with JDK versions 5 through 8 (though OpenJDK only started at 6), you can use BeanShell :

$ docker run --interactive --tty openjdk:8 bash -c "wget --quiet https://github.com/beanshell/beanshell/releases/download/2.1.0/bsh-2.1.0.jar && java -cp bsh-*.jar bsh.Interpreter"
BeanShell 2.1.0 - https://github.com/beanshell/beanshell
bsh %

BeanShell will exit with a typical CTRL-C keypress.

And similar to JShell, you can check the JDK version with:

bsh % System.getProperty("java.version");
<1.8.0_332>

Dotfile alias

We can tie all of the above logic together into a nice Bash function we can add to our dotfiles:

djava() {
    if [[ -z "$1" || "${1:-}" -ge 9 ]]; then
        docker run --interactive --tty "openjdk:${1:-latest}" jshell
    else
        docker run --interactive --tty "openjdk:$1" bash -c "wget --quiet https://github.com/beanshell/beanshell/releases/download/2.1.0/bsh-2.1.0.jar && java -cp bsh-*.jar bsh.Interpreter"
    fi
}

You can find it in mine .

Testing some code

Let's run some code to see what the output is:

jshell> Stream.of(1,2,3,4,5).reduce(0, (a,b) -> a+b);
$1 ==> 15


jshell> double calculateHypotenuse(double a, double b) { return Math.sqrt(a*a+b*b);}
|  created method calculateHypotenuse(double,double)

jshell> calculateHypotenuse(3,4);
$3 ==> 5.0


jshell> 10 / 0;
|  Exception java.lang.ArithmeticException: / by zero
|        at (#4:1)


jshell> IntStream.rangeClosed(0, 10).mapToObj(
   ...>         i -> i % 3 == 0 ?
   ...>                 (i % 5 == 0 ? "FizzBuzz" : "Fizz") :
   ...>                 (i % 5 == 0 ? "Buzz" : i))
   ...>         .forEach(System.out::println);
FizzBuzz
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz