Java has traditionally been a "heavyweight" language not well suited to quick, hacky scripting. That's changed with some of the features added in the last few releases. This is about scripting in Java - NOT Javascript.
I'll cover three features Java has gained in the last few years which means it can now be used as a scripting language.
Firstly, Jshell give us an easy way to try out small snippets of Java without ending up with a mass of tiny Test.java
files scattered around our projects. Secondly, a neat little feature to compile-and-run those tiny test files if we still want to do that. Finally, the shebang feature, which allows Java to be treated as a first class scripting language by the operating system.
Little caveat: I'm not saying this makes Java an ideal scripting language.
I'd argue strongly against saying we should dump Bash, Ksh, Python, Perl, etc and rewrite all our system scripts in Java. Those languages do a good job and I enjoy using them (yes, even Perl, on occasion).
The main point I want to get across here is that if we have a need for a scripting style approach but using Java language makes the most sense, we have the tools to do that now.
Jshell appeared in Java 9, giving Java a REPL (like pretty much every scripting language). Technically, this gave Java another REPL because BeanShell had been around for more than 10 years by then and was doing a pretty good job for those who knew about it (thanks JEdit!)
Anyway, Jshell exists now, is getting (some) support and development from the Oracle/OpenJDK team and gives us our first official way to use Java as a scripting language (ahem, thanks BeanShell).
Within JShell, we can list the history of all the code we've typed so far with /list
, edit that code in a proper editor with /edit
, and save all that stuff we've typed to a file with /save
.
Most importantly, we can further edit that file outside Jshell with our proper editor again, and re-run it all as an input file to the jshell
command - causing the contents to be run as if typed into the REPL - i.e. A SCRIPT - woo!
If we end our input file with a /exit
the REPL will end immediately after running our code. Almost like a real scripting language 😎
As a quick example, imagine we have a file named list-props.jshell
, containing the following
// insert Unix shell style escape codes to make pretty colours
String red(String s) {return "\u001B[31m"+s+"\u001B[0m";}
String green(String s) {return "\u001B[32m"+s+"\u001B[0m";}
System.getProperties().forEach((k,v) -> {
System.out.println(red(k.toString())+": "+green(v.toString()));
});
/exit
We can run that with $ jshell list-props.shell
to see a lovely multi-coloured display of all the default Java properties with their values for the current box:
arc@arc-pc:~/$ jshell list-props.jshell java.specification.version: 15 sun.management.compiler: HotSpot 64-Bit Tiered Compilers sun.jnu.encoding: UTF-8 ...you get the idea...
It's not a particularly amazing example of scripting, or Java, but it shows the idea without too much code getting in the way. I'll use the same core code example for the other examples so won't need to repeat the output
Running code snippets from files via a REPL is ok, but it's not actually running real code, like a proper class definition and all that. So although Jshell code gives us an easy way to knock out quick examples and tests, it doesn't let us quickly knock together and run proper looking Java code. The next little feature helps speed up Java programs that are just a single (proper) Java source file.
Since Java 11, we've been able to do $ java blah.java
to compile and run a single source file in one step. Saving you a whole $ javac blah.java; java blah.class
worth of typing (I did say it was a little feature).
Now we can write something that looks like proper Java, with the following file named ListProps.java
doing the same thing as our list-props.jshell
example above:
public class ListProps {
public static String red(String s) {return "\u001B[31m"+s+"\u001B[0m";}
public static String green(String s) {return "\u001B[32m"+s+"\u001B[0m";}
public static void main(String[] args) {
System.getProperties().forEach((k,v) -> {
System.out.println(red(k.toString())+": "+green(v.toString()));
});
}
}
Running this with $ java ListProps.java
gives us the same result as before, but it looks more like something we can include in a bigger program once we have it working.
We can even put multiple classes into a single source file, and have one of them contain a main()
so the file as a whole is runnable like this. The whole set of standard Java libs is also available, along with any custom libs - if you get the classpath sorted out properly.
Quick naming note: As implied in the note above, when used in this way java
doesn't actually care that your source file name matches the class name inside - because it's not actually creating a .class
file to reference. But you probably do want to stick to that convention anyway; especially if you're planning to move the code into a bigger codebase later on.
Oh, but you definitely do want to keep the .java
extension, because otherwise java
doesn't know you're giving it a source file (unless you pass an extra --source
arg) and it may think you want it to do the cleverness described below.
So a REPL is very nice for knocking out a line or two to check something.
We can string together a load more lines in a .jshell
file to do something neat or prove an idea works.
And that single step compile-and-run trick is helpful for a first pass at classes we can start putting together into our real codebase - especially with that multi-class file trick.
But we all know it isn't proper scripting unless it starts with a shebang.
So how about we just do that? Slap in a shebang line as the first thing in the same file as used above
#!/usr/bin/java --source 11
public class ListProps {
public static String red(String s) {return "\u001B[31m"+s+"\u001B[0m";}
public static String green(String s) {return "\u001B[32m"+s+"\u001B[0m";}
public static void main(String[] args) {
System.getProperties().forEach((k,v) -> {
System.out.println(red(k.toString())+": "+green(v.toString()));
});
}
}
Name that file anything that doesn't end in .java
(to avoid confusing the java
command when it sees our new scripting file) - maybe list-props
or if you're a real Unix fan, shorten it to lp
and expect everyone to just know what it means.
Then $ chmod u+x lp
to let the shell know you really want to run it.
Finally, $ ./lp
will give you the same glorious technicolour result as before
So in conclusion, I say welcome to Java 11's support for shebang script files - and with it, a place at the "well, it can be used as a scripting language if you really want to" table.