Why you need this feature

If you recall the old days just before Java SE 11 (JDK 11), say you have a HelloWorld.java source file that contains a class definition and a static main method that prints out as a single line of text to the terminal:

public class HelloWorld {
      public static void main(String[] args) {
            System.out.println("Hello IBM Developer World");
      }
}

Normally to run this class, first you would need to compile it using a Java compiler (javac), which would result in a HelloWorld.class file.

mohamed_taman$javac HelloWorld.java

Then you would use a java interpreter command to run the resulting class file:

mohamed_taman$ java HelloWorld
Hello IBM Developer World

This starts up the JVM, loads the class, and executes the code.

But what if you want to quickly test a piece of code, or if you’re new to learning Java and want to experiment with the language? These two steps in the process may seem like a bit of heavy-lifting.

In Java SE 11, you get the option to launch a single source code file directly, without compilation.

This feature is particularly useful for someone new to the language who wants to try simple programs; when you combine this feature with jshell, you get a great beginner’s toolset.

Professionals can also make use of these tools to explore new language changes or to try out an unknown API. You can also automate tasks, such as writing Java scripts and then executing them as scripts at the operating system level.

For more information about the new Jshell 10+, see the following two tutorials in this series about this topic:

What you’ll need to follow along

To run all the demos provided in this tutorial you will need to download the Java SE 11 JDK. In this tutorial, I use a plain text editor rather than a Java IDE because I want to avoid any IDE magic and use the Java command line directly throughout the terminal.

Run .java with Java

JEP 330, Launch Single-File Source-Code Programs, is one of the exciting features introduced in the JDK 11 release. This feature allows you to execute a Java source code file directly using the java interpreter. The source code is compiled in memory and then executed by the interpreter.

The feature’s limitations

However, this feature is limited to code that resides in a single source file. You cannot add additional source files to be compiled in the same run.

To work around this limitation, all the classes have to be defined in the same file, but there are no restrictions on the number of classes in the file.

The feature’s simplest example

Now let’s begin the way we always do when we start learning something new — yes exactly as you guessed — with the simplest example: Hello World!

I won’t dive into this feature’s implementation details, but will focus all my efforts on demonstrating how to use this feature by trying out different samples so you get ideas on how the feature can be used in your coding.

If you haven’t already, create the HelloWorld.java file, compile it, and run the resulting class file.

Now I want you to delete the class file; you’ll understand why in a moment:

mohamed_taman$rmHelloWorld.class

Now if you run the class only with the java without compilation as in the following:

mohamed_taman$ java HelloWorld.java
Hello IBM Developer World

you can now just say javaHelloWorld.java. The Java runs out and then you see that we’re passing in just the source code file rather than the class file and it internally compiles this source file then runs the resulting compiled code and finally, the result is shown to the console.

So, there is compilation going on and if there’s a compilation error, you will get an error notification. Also, you can check the directory structure and see that there is no class file generated; it is an in-memory compilation process.

Now let’s see how this happens.

How Java interpreter is able to run HelloWorld

As of JDK 10, the Java launcher operates in three modes:

  1. Running a class file
  2. Running the main class of a JAR file
  3. Running the main class of a module

Now we add a new, fourth mode: Running a class declared in a source file.

The source file mode is determined by two items on the command line:

  1. The first item on the command line that is neither an option nor part of an option
  2. The --source <version> option, if present

For the first item, the Java commands will treat the first item on the command line that is neither an option nor part of an option as a Java source file to be compiled and run. You can still provide options to the Java command before the source file name. For example, if you wish to set a classpath when the source file uses external dependencies.

For the second item, if the filename identifies an existing file with the .java extension, the source-file mode is selected and that file will be compiled and run. The --source option may be used to specify the source version of the source code. I’ll talk more about this later.

In other words, Java will look for the first <FileName>.java. Note that I said file name and not class name.

If the file does not have the .java extension, the --source option must be used to force source-file mode.

This is necessary for cases when the source file is a ¬script to be executed and the name of the source file does not follow the normal naming conventions for Java source files. (There will be more information on this when I discuss shebang files later in this tutorial.)

In the source-file mode, the effect is as if the source file is compiled into memory and the first class found in the source file is executed.

Can you pass command line arguments?

Let’s enhance the Hello World program to create a personalized greeting for any person visiting IBM Developer World:

public class HelloWorld{
    public static void main(String[] args){
        if ( args == null || args.length< 1 ){
System.err.println("Name required");
System.exit(1);
        }
var name = args[0];
System.out.printf("Hello %s to IBM Developer World!! %n", name);
    }
}

Let’s save the code in a file named Greater.java. Notice that the name of the file doesn’t match the name of the public class which violates the Java Language Specifications rules.

Run this code and see what will happen:

mohamed_taman$ java Greater.java “Mo Taman”
Hello Mo Taman to IBM Developer World!!

As you can see it doesn’t matter whether the class name matches the file name; it is compiled in memory and there is no .class file generated.

And I know that you have noticed with your eagle eye how I passed arguments to the code after the name of the file to be executed.

So, any arguments appearing on the command line after the name of the file are passed to the standard main method in this obvious way.

Using the –source option to specify the level of your code file

There are two scenarios to use the --source option:

  1. You can use the source option to specify the source level of your code file
  2. You can force the Java runtime into source execution mode

In the first option, when you omit the source level, it is assumed to be the current JDK version, which will be 11 in our case. With the second option enabled, you can now also pass files with other Java extensions to be compiled and run on the fly.

Now let’s examine the second scenario first and rename Greater.java to just greater without any extension and try to execute it again using the same approach:

mohamed_taman$ java greater "Mo Taman"
Error: Could not find or load main class greater
Caused by: java.lang.ClassNotFoundException: greater

As you see, in the absence of a .java extension, the Java command interpreter is looking for a compiled class by the name provided as the argument. In such scenarios we need to use the --source option to force the source-file mode:

mohamed_taman$ java --source 11 greater "Mo Taman"
Hello Mo Taman to IBM Developer World!!

Now let’s get back to the first scenario. The Greater.java class is compatible with JDK 10 since it contains the var keyword, but is not compatible with JDK 9. Change the source to 10 and see what happens:

mohamed_taman$ java --source 10 Greater.java “Mo. Taman”
Hello Mo. Taman to IBM Developer World!!

Now run the previous command again, but pass to the --source option JDK 9 instead JDK 10:

mohamed_taman$ java --source 9 Greater.java “Mo. Taman”
Greater.java:8: warning: as of release 10, 'var' is a restricted local variable type and cannot be used for type declarations or as the element type of an array
var name = args[0];
            ^
Greater.java:8: error: cannot find symbol
var name = args[0];
        ^
  symbol:   class var
  location: class HelloWorld
1 error
1 warning
error: compilation failed

Fairly simple, right? Now let’s look at using multiple classes.

Can you work with multiple classes?

The answer is yes, you can.

Examine a piece of sample code which contains two classes to show that the code is about to check to determine if a specifically given string is a palindrome or not. A palindrome is a word, phrase, number, or other sequences of characters which reads the same from both directions, such as “madam” or “racecar”.

Here is the code saved in a filename PalindromeChecker.java:

import static java.lang.System.*;
public class PalindromeChecker {
      public static void main(String[] args) {

            if ( args == null || args.length< 1 ){
err.println("String is required!!");
exit(1);
        }
out.printf("The string {%s} is Palindrome!! %b %n",
                  args[0],
                  StringUtils
                        .isPalindrome(args[0]));
      }
}
public class StringUtils {
      public static booleanisPalindrome(String word) {
      return (new StringBuilder(word))
            .reverse()
            .toString()
            .equalsIgnoreCase(word);
      }
}

Now let’s run the file as in the following:

mohamed_taman:code$ java PalindromeChecker.java MadAm
The string {MadAm} is Palindrome!! True

Again, substituting “RaceCar” for “MadAm”:

mohamed_taman:code$ java PalindromeChecker.java RaceCar
The string {RaceCar} is Palindrome!! True

Last, let’s try “Mohamed” in place of “RaceCar”:

mohamed_taman:code$ java PalindromeChecker.java Mohamed
The string {Mohamed} is Palindrome!! False

As you can see, you can add as many public classes as you need in the single source file. The only thing that matters is that the main method should be defined in the first class in the source file. The interpreter (Java command) will use the first class to launch the execution after compiling the code in memory.

Are modules allowed?

Yes, using modules is fully allowed. The in-memory compiled code runs 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 declare dependency using the module-info.java.

Let’s look at code which makes an HTTP call using the new HTTP Client APIs that come with JDK 11. These APIs were introduced in Java SE 9 as an incubator feature, but they have graduated into the java.net.http module.

For more information, read “Java theory and practice: Explore the new Java SE 11 HTTP Client and WebSocket APIs” (IBM Developer, April 2019) — a full tutorial about this feature.

In this example, I am going to call a simple REST API GET method to get some users by calling the endpoint service https://reqres.in/api/users?page=2. The example code is in a file with the name UsersHttpClient.java:

import static java.lang.System.*;
import java.net.http.*;
import java.net.http.HttpResponse.BodyHandlers;
import java.net.*;
import java.io.IOException;
public class UsersHttpClient{
    public static void main(String[] args) throws Exception{
var client = HttpClient.newBuilder().build();
var request = HttpRequest.newBuilder()
.GET()
.uri(URI.create("https://reqres.in/api/users?page=2"))
.build();
var response =
client.send(request, BodyHandlers.ofString());
out.printf("Response code is: %d %n",response.statusCode());
out.printf("The response body is:%n %s %n", response.body());
    }
}

Run it as in the following:

mohamed_taman:code$ java UsersHttpClient.java
Response code is: 200
The response body is:
{"page":2,"per_page":3,"total":12,"total_pages":4,"data":[{"id":4,"first_name":"Eve","last_name":"Holt","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg"},{"id":5,"first_name":"Charles","last_name":"Morris","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/stephenmoon/128.jpg"},{"id":6,"first_name":"Tracey","last_name":"Ramos","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/bigmancho/128.jpg"}]}

This allows you to quickly test any new features in different modules without having to create a module. You can also define the module-info files for that module and so on.

See JSR 376 for more information on the Java Platform Module System.

Scripting, why we need it, and why it is important

First, let’s understand what scripting is in order to understand why it is an important component for the Java programming language.

We can define scripting as the following:

“It is a program(s) written for a special run-time environment that automate the execution of task(s) that could alternatively be executed one-by-one by a human.”

From this generic definition, we can derive a simple definition for a script language: A scripting language is a programming language that employs a high-level construct to interpret and execute one command at a time:

  • Scripting uses a series of commands within a file that is capable of being executed without being compiled — in general, scripting languages are often interpreted rather than compiled
  • They are easier to learn and you can code faster using them, than with the more structured and compiled languages such as Java, C, and C++

Good examples of server-side scripting languages include Perl, PHP, and Python; on the client-side is JavaScript.

For a long time, Java was categorized as well structured, strongly typed, and complied language, only interpreted by the JVM to run on any computer architecture. This was the main complaint about Java; that it wasn’t as fast to learn or to prototype with like other scripting languages.

But Java is a well established language now and is used by about nine-and-a-half million developers worldwide. To make is easier to learn and try out its features and APIs without having to engage the complex process of compilation, the release of Java SE 9 saw the addition of the JShell REPL, a tool that supports interactive programming.

The JShell REPL makes Java easier to learn and run experiments on quickly. If you don’t believe me, jump over to the two hands-on tutorials in this series on using JShell and give them a read.

So, how do you do scripting in Java 11? You run your code through invoking the java command in two basic ways:

  • Using the java command tool directly
  • Using *nix terminal Line Scripts as bash script

We’ve already explored the first option, so now let’s explore the second option.

Shell scripting with Java: Shebang files

Remember, Java SE 11 introduces support for scripting with traditional *nix shebang files. No changes to the Java Language Specification are required to support of this feature.

In a general shebang file, the first two bytes must be “0x23” and “0x21”, the two-character ASCII encoding of “#!”. All subsequent bytes are read with the default platform character encoding that is in effect.

A first line beginning with #! is only required when you want to execute the file with the operating system’s shebang mechanism. You do not need a special first line when the Java launcher is used explicitly to run the code in a source file, like in the HelloWorld.java example we did earlier.

Here are some other important rules for you to know when creating a shebang file.

Rules for creating a shebang file
Don’t mix Java code with your desired operating system shell scripting language.
If you’re required to include virtual machine options, you must specify the --source as the first option following the name of the executable in the shebang file. These options include: --class-path, --module-path, --add-exports, --add-modules, --limit-modules, --patch-module, --upgrade-module-path, and any variant forms of those options. It also could include the new --enable-preview option as described in JEP 12.
You must specify the version of the Java language used for the source code in the file.
The shebang characters (#!) must be the first line of the file and it should look like this: #!/path/to/java --source <version>.
The use of the shebang mechanism to execute files that follow the standard naming convention (files ending with .java) for Java source files is not allowed.
Finally, you must mark the file as executable using chmod +x <Filename>.<Extension>.

It’s show time; let’s create a shebang file (script utility program) that will list the content of a passed directory as parameter. And if the directory doesn’t pass, the current directory will be listed by default. This example is running on a terminal using MacOS Mojave 10.14.3.

The source code is:

#!/usr/bin/java --source 11
import java.nio.file.*;
import static java.lang.System.*;
public class DirectoryLister {
      public static void main(String[] args) throws Exception {
            vardirName = ".";
            if ( args == null || args.length< 1 ){
err.println("Will list the current directory");
        } else {
      dirName = args[0];
        }
            Files
            .walk(Paths.get(dirName))
            .forEach(out::println);
      }
}

Save this code in a file named dirlist without any extension and then mark it as executable:

mohamed_taman:code$chmod +x dirlist

Run it as the following:

mohamed_taman:code$ ./dirlist
Will list the current directory
.
./PalindromeChecker.java
./greater
./UsersHttpClient.java
./HelloWorld.java
./Greater.java
./dirlist

Run it again with the following command passing the parent directory and check the output yourself.

mohamed_taman:code$ ./dirlist ../

Notice that the shebang line (the first line) is ignored by the interpreter when evaluating source code. A shebang file can also be invoked explicitly by the launcher, perhaps with additional options like this: $ java -Dtrace=true --source 11 dirlist.

Also, if the script file is in the current directory, you could execute it like this:

$ ./dirlist

Or, if the script is in a directory in the user’s PATH, you could execute it like this:

$ dirlist

Finally, I want to show you some tricks and pitfalls to be aware of when using this feature.

Tips, tricks, and pitfalls

Here are some tricky situations you might face while using this feature.

Options may not pass or be recognized

Some options that you can pass to javac may not be passed (or recognized for that matter) by the java tool, such as -processor or -Werror options.

When you’re forced to use the class file

If both .class and .java files exist in the classpath, you’re forced to use the class file.

mohamed_taman:code$ javac HelloWorld.java
mohamed_taman:code$ java HelloWorld.java
error: class found on application class path: HelloWorld

Watch for class and package naming conflicts

Bear in mind the class and package naming conflicts. Check the following directory structure:

mohamed_taman:code$ tree
.
├── Greater.java
├── HelloWorld
│   ├── java.class
│   └── java.java
├── HelloWorld.java
├── PalindromeChecker.java
├── UsersHttpClient.java
├── dirlist
└── greater

Now notice these two files; java.java under the HelloWorld package and the file HelloWorld.java in the current directory. Do you see the problem? It’s a nasty one. What happens when you try to run:

mohamed_taman:code$ java HelloWorld.java

Which file will run, the first one or the second one?

java launcher is no longer referencing the class file in the HelloWorld package. Instead, it will load the HelloWorld.java file from source. The result is that the file in the current directory will run.

I love this shebang feature because it opens a world of possibilities to create scripts that can automate a lot of your work using the power of the Java language.

You can get the source code for this tutorial from GitHub.

Summary

Starting with Java SE 11 and for the first time in the programming language’s history, you can execute a script containing Java code directly without compilation. The Java 11 source execution feature makes it possible to write scripts in Java and execute them directly from the *nix command line.

Start experimenting with this new feature today.