It's no secret that one of the compelling reasons to choose Java for development it's the sheer amount of libraries and frameworks to choose from. The use of said libraries typically requires a build system and bundling dependencies with the production code, otherwise execution will fail. Thus build tools such as Apache Maven and Gradle have become a staple of Java development around the world, offering a range of packaging and deployment options. However there are times when you'd like to have a much quicker turnaround between writing code and execution, something like a REPL or a script that lets you evaluate code on the fly. Java 9 introduced JShell and JEP 330 (Launch Single-File Source-Code Programs) but both options lack the ability to download and cache dependencies. Bummer. I guess we can't script with Java, or can we?
Today I'll demonstrate 2 options that you have at your disposal to create self contained scripts, that is, scripts that define their required dependencies, allowing you to share the scripts across different servers or with your team mates without worrying about setting up dependency resolution nor packaging. These options are Apache Groovy and jbang.
From the main page we learn that "Apache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities, for the Java platform aimed at improving developer productivity thanks to a concise, familiar and easy to learn syntax. It integrates smoothly with any Java program, and immediately delivers to your application powerful features, including scripting capabilities, Domain-Specific Language authoring, runtime and compile-time meta-programming and functional programming."
Groovy emerged in 2003 as a response to a simple question: what if Java had features similar to Ruby and Python? In the following years the Groovy programming language became Java's dynamic buddy, which complements Java in many ways. The Groovy syntax is perhaps the closest to Java than any other alternative JVM language currently available; it's so flexible that it lets you write code as close to Java or as idiomatically Groovy as you want.
Groovy sports a dependency manager that accepts dependency hints baked into the production code, this is known as the Grapes dependency manager. In a nutshell, you simply define dependencies using the short GAV (groupId, artifactId, version) notation and Grapes will download them at the appropriate time. These definitions are placed into code by means of annotations, the primary one being @Grab. You may annotate a class with this annotation, or you may annotate an import statement. That's right, that's one the neat features found in the Groovy syntax. The following example shows an idiomatic Groovy script that can connect to an Oracle database to check that said database is up and accepting connections.
This script requires a set of System properties that define values used to connect to the database such as URL, username, and password. Take note that the script simply establishes a connection to the database and uses a single query to check if the database is responsive. Given that connecting to an Oracle database requires a specific set of drivers, and those drivers are shipped as JARs available from Maven Central it makes sense to specify the GAV coordinates for the drivers. The script may be invoked in the following way
groovy -Ddb.url=jdbc:oracle:thin:@myserver:1521/myservice \ -Ddb.username=dbuser \ -Ddb.password=s3cr3t \ db.groovy
If you'd like to test this script with your own setup you'll need to download Groovy unto your system, there are many ways to do this but I'd recommend using SDKMAN!. You'll also have to provide suitable values for db.url, db.username, and db.password that match your own settings. Now, recall from earlier when I mentioned that Groovy's syntax is variable, the following script provides the same behavior albeit with some modifications
This is still a script even though it contains a class definition. You can also appreciate that the code looks closer to Java than before, with just a few Groovyisms sprinkled here and there; the code is statically compiled as well, which means the IDE will raise compile errors, neat! This script may be invoked as follows
groovy -Ddb.url=jdbc:oracle:thin:@myserver:1521/myservice \ -Ddb.username=dbuser \ -Ddb.password=s3cr3t \ db2.groovy
In other words, it can be invoked just like the first script. It's worth saying that you can use Java 8 up to latest Java to run these scripts. Now, even though Groovy code can be as close to Java code you may still have some reservations in using a language that is not strict Java, this is where jbang comes in.
jbang is a fairly recent addition (Dec 2019) to the Java tooling space. It began life as an experiment to leverage JEP 330 plus dependency resolution and has quickly expanded to support more features. JEP 330 became available in Java 10 however jbang will let you run scripts with Java 8 as a minimum, though Java 11 is recommended. jbang requires a special file header to define dependencies, the notation is quite similar to Groovy's @Grab, in fact you may even use @Grab if you want to. The difference with the previous snippets of course is that the following are 100% Java code. Here's how jbang can be used to provide a Java script
This file can be launched in a similar fashion as we did with its Groovy counterpart, like so
jbang -Ddb.url=jdbc:oracle:thin:@myserver:1521/myservice \ -Ddb.username=dbuser \ -Ddb.password=s3cr3t \ db.java
jbang may be installed in many ways, here I'd also recommend using SDKMAN! to accomplish this task. Note that this snippet is quite close to the second Groovy snippet, demonstrating once again how close Java and Groovy are in terms of syntax. Finally, you may remember I mentioned JShell early in this post. JShell is Java's REPL available since Java 9, which lets you write script like statements. It also lets you load a set of instructions from file. Would it be possible to use this facility paired with jbang's dependency resolution? Why yes, of course it's possible, the following snippet shows how
Note the change in the file's extension, also there's no longer a class definition. In a sense this snippet is closer to the first Groovy script shown in this post. This script can be executed just like its predecessor
jbang -Ddb.url=jdbc:oracle:thin:@myserver:1521/myservice \ -Ddb.username=dbuser \ -Ddb.password=s3cr3t \ db.jsh
And there you have it, whether you prefer an optional/dynamic/statically typed language like Groovy or plain Java you have options to write and execute scripts that leverage the wide array of JARs currently available out there in the wild.
Image by Sven Lachmann