Using external libraries in JShell

How to load external JARs and project classes to JShell

After I’ve experimented with JShell to solve mathematical puzzles with the Java’s Standard Library, I set out to use it for two other use cases: investigate external libraries, and use the REPL experiment with classes defined in a project.

Loading external classes with vanilla JShell

Ideally, JShell itself would offer a resolution mechanism to grab external dependencies but currently, there’s no such feature. There’s a custom version of JShell which provides the /resolve command to download artifacts, but it’s not maintained anymore.

Although external JAR files can be imported via classpath, this alone is pretty cumbersome to try third-party libraries. A typical library is available on a Maven repository, and normally it’s consumed by a dependency management tool. So first, you have to download the JAR file manually, and put it on the classpath. If it has any dependencies, those have to be downloaded as well. Finally, you have to craft the classpath yourself.

Also, I’ve found out the hard way that there’s a strange bug in JShell: packages in classpath don’t always appear in completions. If the classpath is specified via the --class-path argument, autocompletions will not list the external packages.

# Set classpath via --class-path
jshell --class-path=”...classpath with Guava...”
|  Welcome to JShell -- Version 9.0.1
|  For an introduction type: /help intro

jshell> import com.google<TAB>
# No autocompletion!
jshell> com.google.common.base.Strings.repeat("I'm here!", 3)
$1 ==> "I'm here!I'm here!I'm here!"

However, if the classpath is specified using the CLASSPATH environment variable, it works:

# Set classpath in Bash (in Windows, use SET instead of export)
export CLASSPATH=”...”

# Start JShell using the custom classpath defined above
jshell
# Set classpath via --class-path
jshell --class-path=”...classpath with Guava...”
|  Welcome to JShell -- Version 9.0.1
|  For an introduction type: /help intro

jshell> import com.<TAB>
google.   sun.
jshell> import com.google.
common.       errorprone.   j2objc.       thirdparty.

To summarize, while it’s possible to download dependencies by hand and craft the classpath manually, it’s really tedious.

Build tool integration

To make things a bit easier, I decided to see how JShell could be integrated into a Maven or Gradle project which already solves the problem of dependency resolution. A plugin exists for both build tools (jshell-maven-plugin, jshell-gradle-plugin) that allows launching JShell with the project’s classpath.

One problem with these plugins is that they are using the --class-path argument under the hood, so they are affected by the bug I’ve mentioned in the previous section. The solution is simple: even without the plugins, it’s quite easy to get the project’s classpath directly from the build tool and use it in the CLASSPATH environment variable.

For a Maven project, the Maven Dependency Plugin’s build-classpath goal can be used for this purpose (note that the usual verbose is suppressed by the grep command which leaves only the classpath):

export CLASSPATH=$(mvn dependency:build-classpath | grep -vE "[INFO]|[ERROR]")
jshell

It’s not as easy for Gradle, but with a simple task, you can print the classpath…

task printClasspath {
   doLast {
       println sourceSets.main.runtimeClasspath.asPath
   }
}

… which can be used to fill the environment variable (again, the -q flag is necessary to suppress the other noises from the build log):

export CLASSPATH=$(gradle -q printClasspath)
jshell

Both of these approaches allow us to load external libraries and experiment with classes defined in the project.

jshell-resolve

Occasionally I want to quickly check how a library works without opening an IDE or altering a project. To make this easier, I’ve put a custom bash script called jshell-resolve to my PATH that expects one or more Maven Coordinates as arguments, resolves them using a generated Gradle build and starts JShell with all the artifacts available on the classpath.

For example, with jshell-resolve com.google.guava:guava:28.2-jre JShell starts with Guava:

using jshell-resolve

The script launches a Docker container including a JDK, Gradle and an empty Gradle project with the specified dependencies. The classpath will only contain the specified packages.

IDE support

In IntelliJ IDEA there’s built-in support for JShell, which can be launched with the project’s classpath. Although the experience is a bit different than the terminal-based shell, as there are separate input and output windows. (For an example, see the Java 9 and IntelliJ IDEA post from the JetBrains blog.)

The support is not so nice for Eclipse though. I’ve tried to use JShell as an External Tool suggested by this post, but autocompletion did not work in the Console window. Sadly, I’ve found no viable alternatives other than the old Scrapbook Page feature, (suggested in this SO thread) which is not quite the same as JShell.

Summary

This post covered alternatives about accessing your classes in JShell. It’s really great to see a shift from the IDE oriented Java language to having a REPL, which is a great addition to the ecosystem.

14 January 2020