BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Running Single-file Programs without Compiling in Java 11

Running Single-file Programs without Compiling in Java 11

This item in japanese

Lire ce contenu en français

Bookmarks

Key Takeaways

  • This feature provides the ability to run a Java single-file source code directly without any compilation, avoiding tedious steps that involved previously to run just a simple hello world program.
  • This feature is particularly useful for someone new to the language who wants to try out simple programs or features; when you combine this feature with jshell, you get a great beginner’s learning toolset.
  • This feature takes you also to advanced levels, where you can pass command-line parameters to control it, work with more classes, and even add modules to current application in a single run.
  • Combine this feature with Shebang files (#!), you can normally run Java as a shell script, as you run you *nix bash script from command-line.
  • This article explores the new Java 11+ Launch Single-File Source-Code Programs (JEP 330) and provides JShell-based examples and tips and tricks of the correct and incorrect usage.

Why you need this

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

public class HelloUniverse{
      public static void main(String[] args) { 
            System.out.println("Hello InfoQ Universe");
      }
}

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

mohamed_taman$ javac HelloUniverse.java

Then you would use a java virtual machine (interpreter) command to run the resulting class file:

mohamed_taman$ java HelloUniverse
Hello InfoQ Universe

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 (which is the key here) and want to experiment with the language? Those 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 intermediate compilation.

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

For more information about the new Jshell 10+, please check out my video course "Hands-on Java 10 Programming with JShell".

Professionals can also make use of these tools to explore new language changes or to try out an unknown API. In my opinion, greater power comes when we can automate a lot of tasks such as writing Java programs as scripts and then executing them from the operating system shell. This combination gives us the flexibility of shell scripts but with the power of Java language. We will explore this in more detail in the second half of the article.

This great Java 11 feature allows you to run a Java single-file source code directly without any compilation. So now let’s dive into more of the details and interesting related topics.

What you’ll need to follow along

To run all the demos provided in this article you will need to use a recent version of Java. It should be Java 11 or later. The current feature release is Java SE Development Kit 12.0.1 - the final version is available from this link, just accept the license and click the link relevant to your operating system. If you want to explore more recent features, the latest JDK 13 early access is the most up-to-date, and you can download it from this link.

You should also note that OpenJDK releases are now also available, from Oracle as well as other vendors, including AdoptOpenJDK.

In this article, we use a plain text editor rather than a Java IDE because we 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 new 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, without producing a .class file on disk.

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, and either they are all public classes or not it doesn’t matter as long as they are in the same source file. 

The first class declared in the source file will be picked up to be used as the main class, and we should put our main method inside that first class. So the order matter.

A first 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 Universe!

We will focus our efforts on demonstrating how to use this feature by trying out different samples so you get ideas of how the feature can be used in your day to day coding.

If you haven’t already, create the HelloUniverse.java file as listed at the top of the article, 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$ rm HelloUniverse.class

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

mohamed_taman$ java HelloUniverse.java
Hello InfoQ Universe

You should see the same result as before - it runs.

This means we can now just say java HelloUniverse.java. We're just passing in the source code rather than the class file - it internally compiles the source, then runs the resulting compiled code and finally, the message is output to the console.

So, there is still a compilation process going on and if there's a compilation error, you will still 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 magic happens.

How Java interpreter runs the HelloUniverse program

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 in Java 11, a new fourth mode has been added

  1. Running a class declared in a source file.

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.

The decision to enter 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 case, the Java command will look at the first item on the command line that is neither an option nor part of an option. If it is a filename that ends in .java then it will be treated 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 case, the source-file mode is selected and the first non-option command line item will be treated as a source file to be compiled and run. 

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. 

The --source option may be used to specify the language version of the source code. I’ll talk more about this later.

Can we pass command-line arguments?

Let’s enhance the Hello Universe program to create a personalized greeting for any person visiting the InfoQ universe:

public class HelloUniverse2{
    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 InfoQ Universe!! %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 rules of the Java Language Specification

Run this code and see what will happen:

mohamed_taman$ java Greater.java "Mo. Taman"
Hello, Mo. Taman to InfoQ universe!!

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. Eagle-eyed readers may also have noticed how we passed arguments to the code after the name of the file to be executed. This means that any arguments appearing on the command line after the name of the file are passed to the standard main method in this obvious way.

Specify the level of your code file with --source option

There are two scenarios that make use of the  --source option:

  1. Specify the source level of your code file
  2. Force the Java runtime into source execution mode

In the first case, when you omit the source level, it is assumed to be the current JDK version. In the second case, you can pass filenames with extensions other than .java to be compiled and run on the fly.

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 - mode 1 of the java launcher. To prevent this,  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 InfoQ universe!!

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

mohamed_taman$ java --source 10 Greater.java "Mo. Taman"
Hello Mo. Taman to InfoQ universe!!

Now run the previous command again, but pass to the --source option JDK 9 instead of 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

Notice the form of the error message - the compiler warns about var becoming a restricted type name in JDK 10, but because this is language level 9, compilation still proceeds. However, the attempt to compile then fails because a type called var cannot be found within the source file.

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

Does it work with multiple classes?

The answer is yes, it does.

Let’s examine a piece of sample code which contains two classes to demonstrate that the feature works with multiple classes. The code checks to determine if a 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 "redivider" or "reviver".

Here is the code saved in a file named 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 a Palindrome!! %b %n",
                  args[0],
                  StringUtils
                        .isPalindrome(args[0]));            
      }
}
public class StringUtils {
      public static Boolean isPalindrome(String word) {
      return (new StringBuilder(word))
            .reverse()
            .toString()
            .equalsIgnoreCase(word);
      }
}

Now let’s run the file:

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

Let’s do it again, substituting "RaceCar" for "MadAm":

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

Finally, let’s try "Mohamed" in place of "RaceCar":

mohamed_taman:code$ java PalindromeChecker.java Taman
The string {Taman} is a 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 as the entry point to launch the program 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 which gives access to the full range of modules that ship with the JDK.

This enables the code to use different modules without the need to explicitly declare dependency using module-info.java.

Let’s look at some code that makes an HTTP call using the new HTTP Client API that came with JDK 11. Note that these APIs were introduced in Java SE 9 as an incubator feature, but they have now graduated into a full feature in the java.net.http module.

In this example, we are going to call a simple REST API via a GET method to get some users. We’ll call a public 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());     
    }
}

Running the program resulting in the following output:

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 new features provided by different modules without having to create a module of your own. 

For more information about the new Java Platform Module System (JPMS), please take a look at my video course "Getting Started with Clean Code Java SE 9".

Why scripting is important to Java?

First, let’s remind ourselves what scripting is, in order to understand why it is important to have it available in the Java Programming language.

We can define the scripting as the following:
"A script is a program written for a specific run-time environment that automates the execution of tasks or commands that could alternatively be executed one-by-one by a human."

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

A scripting language is a programming language that uses a series of commands within a file. Generally, scripting languages are often interpreted (rather than compiled), and tend towards the procedural style of programming (although some scripting languages have object-oriented features as well)

In general, scripting languages are easier to learn and faster to code in, than more structured, compiled languages such as Java, C, and C++. Examples of server-side scripting languages including Perl, PHP, and Python, and on the client-side JavaScript.

For a long time, Java was categorized as a well structured, strongly typed, compiled language, interpreted by the JVM to run on any computer architecture. However, one complaint about Java is that it is not as fast to learn or prototype with when compared to common scripting languages.

However, Java is now a 24-year-old language and is used by around 9.4 million developers worldwide. To make it easier for younger generations of programmers to learn Java and try out its features and APIs without the need for the ceremony of compilation and IDEs, several features have been added in recent releases. Starting with  Java SE 9 a JShell (REPL) tool that supports interactive programming has been added, with the intent of making Java easier to program and learn.

Now, with JDK 11, Java moves toward being a programming language that can support scripting, as you can run your code simply through invoking the java command!

There are two basic ways to do scripting in Java 11:

  1. Using the java command tool directly.
  2. Using *nix command-line scripts - similar to a bash script.

We have already explored the first option, so now it is time to take a look at the second option, which is a feature that opens the door to a lot of possibilities.

Shebang files: run Java as a shell script.

As mentioned before, Java SE 11 introduces support for scripting - including the traditional *nix so-called shebang files. No changes to the JLS (Java Language Specification) are required to support this feature.

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

Therefore, a first-line beginning #! is only required if it is desired to execute the file with the operating system's shebang mechanism. This means that there is no need for any special first line when the Java launcher is explicitly used to run the code from a source file, as in the HelloUniverse.java example above.

Let’s run the next example in a Terminal running on macOS Mojave 10.14.5. But first, we should lay out some important rules to follow when creating a shebang file:

  • Don’t mix Java code with your operating system’s shell scripting language.
  • If you’re required to include VM (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, 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>.

For our example, let’s create a shebang file (script utility program) that will list the content of a directory the name of which is passed as a parameter. If no parameters are passed, the current directory will be listed by default.

#!/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 ../

Note: the shebang line (the first line) is ignored by the interpreter when evaluating source code. Therefore a shebang file can also be invoked explicitly by the launcher, perhaps with additional options as the following:

$ java -Dtrace=true --source 11 dirlist

Also, it is worth to note that, if the script file is in the current directory, you could execute it as the following:

$ ./dirlist

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

$ dirlist

Finally, let’s close by showing some tips and tricks to be aware of when using this feature.

Tips and tricks

  1. 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.
  2. If both .class and .java files exist in the classpath, the launcher forces you to use the class file.
mohamed_taman:code$ javac HelloUniverse.java
mohamed_taman:code$ java HelloUniverse.java
error: class found on application class path: HelloUniverse
  1. Bear in mind the possibility of class and package naming conflicts. Look at the following directory structure:

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

    Notice the two files java.java under the HelloUniverse package and the file HelloUniverse.java in the current directory. What happens when you try to run:

    mohamed_taman:code$ java HelloUniverse.java

    Which file will run, the first or the second one? The java launcher is no longer referencing the class file in the HelloUniverse package. Instead, it will load and run the HelloUniverse.java file from source, so that the file in the current directory will run. 

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

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 *inx command line.

Start experimenting with this new feature today, and happy coding. Please say thanks by spreading the word, just to share it with your fellow geeks.

Resources

About the Author

Mohamed Taman is Sr. Enterprise Architect @DevTech d.o.o, a Java Champions, An Oracle Groundbreaker Ambassador, Adopts Java SE.next(), JakartaEE.next(), a JCP member, Was JCP Executive Committee member, JSR 354, 363 & 373 Expert Group member, EGJUG leader, Oracle Egypt Architects Club board member, speaks Java, love Mobile, Big Data, Cloud, Blockchain, DevOps. An International speaker, books & videos author of "JavaFX essentials", "Getting Started with Clean Code, Java SE 9", and "Hands-On Java 10 Programming with JShell", and a new book "Secrets of a Java Champions", Won Duke’s choice 2015, 2014 awards, and JCP outstanding adopt-a-jar participant 2013 awards.

Rate this Article

Adoption
Style

BT