Launch Single-File Source-Code Programs in JDK 11
JEP 330 – Launch Single-File Source-Code Programs is one of the exciting features in the upcoming JDK 11(18.9) release. This feature allows executing your java source code directly using the java
interpreter. The source code is compiled in memory and then executed by the interpreter. The limitation is that all the classes have to be defined in the same file.
This feature is particularly useful for someone who is starting off with learning Java and wants to try to simple programs. This feature along with jshell
will be a great toolset for any beginners in learning the language. Not only them, but professionals can also make use of these tools to explore new language changes or to try out an unknown API.
In this post, I will not go into the details of how this feature is implemented, instead, I will focus on using this feature by trying out different samples. Let’s start with the simplest example, as usual, the Hello World!
Simplest Example
The below code is saved in a file HelloWorld.java
public class HelloWorld{ public static void main(String[] args){ System.out.println("Hello World!!!"); } }
I will run the above code as shown below:
PS G:\samples\java11\single-file> java HelloWorld.java Hello World!!!
In the above example, there is only one class and it contains the main
method. While executing the code using java
we need to pass to it the name of the file ending in .java
extension. If the filename doesn’t end with .java
extension then we have to use the option --source
as we will see in the next example.
With Command Line arguments
Let’s enhance the Hello Worl program to create a personalized greeting for each person:
public class Greeting{ public static void main(String[] args){ if ( args == null || args.length < 1 ){ System.err.println("Name required"); System.exit(1); } System.out.println(String.format("Hello %s!!", args[0])); } }
Lets store the above code in a file named HelloGreeting.java
. Notice that the name of the file doesn’t match the name of the public class. Let us run the above code using:
PS G:\samples\java11\single-file> java HelloGreeting.Java sana Hello sana!!
Any arguments to be passed to the code as provided after the name of the file to be executed. Let’s rename HelloGreeting.java
to just greeting
and try to execute using the same approach:
PS G:\samples\java11\single-file> java greeting sana Error: Could not find or load main class greeting Caused by: java.lang.ClassNotFoundException: greeting
You can see that in the absence of .java
the interpreter is looking for a compiled class by the name provided as the argument. In such scenarios we need to use the --source
option as shown below:
PS G:\samples\java11\single-file> java --source 11 greeting sana Hello sana!!
Let’s me show you how code written for JDK 10 will not work for JDK 9 when we use the --source
option:
public class Java10Compatible{ public static void main(String[] args){ var message = "Hello world"; System.out.println(message); } }
Let’s execute the above for JDK 10 and JDK 9 as show below:
PS G:\samples\java11\single-file> java --source 10 Java10Compatible.java Hello world PS G:\samples\java11\single-file> java --source 9 Java10Compatible.java .\Java10Compatible.java:3: error: cannot find symbol var message = "Hello world"; ^ symbol: class var location: class Java10Compatible 1 error error: compilation failed
Multiple classes in a single file
As I mentioned before this feature supports running code which resides in a single file, there are no restrictions on the number of classes in the file. Let’s look at a sample code which contains two classes:
public class SimpleInterest{ public static void main(String[] args){ if ( args == null || args.length < 3 ){ System.err.println("Three arguments required: principal, rate, period"); System.exit(1); } int principal = Integer.parseInt(args[0]); int rate = Integer.parseInt(args[1]); int period = Integer.parseInt(args[2]); double interest = Maths.simpleInterest(principal, rate, period); System.out.print("Simple Interest is: " + interest); } } public class Maths{ public static double simpleInterest(int principal, int rate, int period){ return ( principal * rate * period * 1.0 ) / 100; } }
Let’s run this:
PS G:\samples\java11\single-file> java .\SimpleInterest.java 1000 2 10 Simple Interest is: 200.0
In the case of a file with more than one class defined, the first class should contain the main method and the interpreter after compiling in memory will use the first class to launch the execution.
Using modules
The in-memory compiled code is run as part of an unnamed module with the option --add-modules=ALL-DEFAULT
. This enables the code to use different modules without the need to explicitly declaring dependency using the module-info.java
Let’s look at the code which makes an HTTP call using the new HTTP Client APIs. These APIs which were introduced in Java 9 in as incubator feature have been moved out from the incubator into java.net.http
module. The example code is:
import java.net.http.*; import java.net.http.HttpResponse.BodyHandlers; import java.net.*; import java.io.IOException; public class ExternalModuleDepSample{ public static void main(String[] args) throws Exception{ HttpClient client = HttpClient.newBuilder().build(); HttpRequest request = HttpRequest.newBuilder() .GET() .uri(URI.create("https://reqres.in/api/users")) .build(); HttpResponse<String> response = client.send(request, BodyHandlers.ofString()); System.out.println(response.statusCode()); System.out.println(response.body()); } }
We can run the above code by issuing the following command:
PS G:\samples\java11\single-file>java ExternalModuleDepSample.java 200 {"page":1,"per_page":3,"total":12,"total_pages":4, "data":[{"id":1,"first_name":"George","last_name":"Bluth", "avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/calebogden/128.jpg"}, {"id":2,"first_name":"Janet","last_name":"Weaver", "avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/josephstein/128.jpg"}, {"id":3,"first_name":"Emma","last_name":"Wong", "avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/olegpogodaev/128.jpg"}]}
This allows us to quickly test new features in different modules without going through creating modules, module-info
files and so on.
Shebang files
In this section, we will look at creating shebang files. Shebang files are those files which can be executed directly on the Unix systems by providing the executor using the syntax #!/path/to/executable
as the first line of the file.
Let’s create a shebang file:
#!/g/jdk-11/bin/java --source 11 public class SimpleInterest{ public static void main(String[] args){ if ( args == null || args.length < 3 ){ System.err.println("Three arguments required: principal, rate, period"); System.exit(1); } int principal = Integer.parseInt(args[0]); int rate = Integer.parseInt(args[1]); int period = Integer.parseInt(args[2]); double interest = Maths.simpleInterest(principal, rate, period); System.out.print("Simple Interest is: " + interest); } } public class Maths{ public static double simpleInterest(int principal, int rate, int period){ if ( rate > 100 ){ System.err.println("Rate of interest should be <= 100. But given values is " + rate); System.exit(1); } return ( principal * rate * period * 1.0 ) / 100; } }
The source option in the shebang is used in cases where the name of the file doesn’t follow the standard java filename naming convention. In our case, we have saved the above code in a file called simpleInterest
and we can run this as:
sanaulla@Sana-Laptop /g/samples/java11/single-file (master) $ ./simpleInterest 1000 20 2 Simple Interest is: 400.0
On Windows machines, I have used the bash shell that comes with the git
installation. There are multiple other ways like Cygwin, Windows 10 Ubuntu Support and so on.
The source code for this can be found here.
Published on Java Code Geeks with permission by Mohamed Sanaulla, partner at our JCG program. See the original article here: Launch Single-File Source-Code Programs in JDK 11 Opinions expressed by Java Code Geeks contributors are their own. |
This is actually pretty cool for simple, one-off programs. However I could see things becoming quite messy if someone tries to put a large project with hundreds of classes in a single file.
Yes as I mentioned in the beginning good for learning the language and also new APIs in the language. Very powerful learning tool along with jshell. Another advantage is the shebang files which can be used to write some one of scripts in java.
Hey! Any examples how to specify class path in order to access external libs?