Java 11: Run Single-File Source Code Programs with java

Introduction Before Java 11, running even the smallest Java program took two steps - compiling the .java file with javac, and then running the .class file with java. Java 11 (JEP 330) lets the java launcher run a single-file source code program directly. This tutorial explains how to run a Java source file with the java command, with examples. We will first run a program and pass it arguments, and see what happens behind the scenes. We will then put several classes in one file, choose the Java version of the source code, and use libraries from the class path. Lastly, we will write a shebang script which runs on Linux and macOS, and go through the limitations of the launcher.

(Note - To see how programs were compiled and run before Java 11, read compiling and running Java programs with javac and javaClick to read: compiling and running Java programs with javac and java.) Running a source file with java Let us take a simple program which greets the names passed to it as arguments -
A single-file program
//Greet.java
public class Greet {
  public static void main(String[] args) {
    if (args.length == 0) {
      System.out.println("Usage: java Greet.java <name>...");
      return;
    }
    for (String name : args) {
      System.out.println("Hello, " + name + "!");
    }
  }
}
Instead of compiling it first, we pass the source file name, including its .java extension, to the java command. Everything after the file name is passed to the program as arguments -
java Greet.java Tom Harry
 OUTPUT of the above command
Hello, Tom!
Hello, Harry!
What happens behind the scenes
  • When the file name ends with .java, the launcher runs in source-file mode. It compiles the file in memory, loads the compiled classes, and runs the main method of the first top-level class in the file.
  • No .class file is written to disk. The program is compiled again every time it is run, which adds a little to the startup time.
  • The command is roughly equivalent to running javac -d <memory> Greet.java followed by java -cp <memory> Greet Tom Harry.
  • If the file does not compile, the compilation errors are printed and the program is not run.
Source-file mode is meant for small programs and scripts, for trying out an idea, and for beginners writing their first programs. Larger applications are still compiled with javac or a build tool such as Maven or Gradle.
Several classes in one file The source file can contain more than one top-level class. The launcher runs the first top-level class in the file, so that class must contain the main method. The file name does not need to match the class name -
A source file with several classes
//StudentReport.java
import java.util.List;

public class StudentReport {
  public static void main(String[] args) {
    List<Student> students = List.of(new Student("Tom", 78), new Student("Harry", 91));
    students.forEach(s -> System.out.println(s.name + " -> " + Grader.grade(s.marks)));
  }
}

class Student {
  final String name;
  final int marks;
  Student(String name, int marks) {
    this.name = name;
    this.marks = marks;
  }
}

class Grader {
  static String grade(int marks) {
    return marks >= 90 ? "A" : marks >= 75 ? "B" : "C";
  }
}
 OUTPUT of the above command
Tom -> B
Harry -> A
Choosing the source version with --source By default the source file is compiled for the version of the JDK you are running. The --source option compiles it for an older version instead, which is useful to check that a script still works on an older JDK. For example, the program below uses a switch expression, a feature added in Java 14 -
A program using a feature newer than Java 11
//Season.java
public class Season {
  public static void main(String[] args) {
    int month = 7;
    String season = switch (month) {
      case 12, 1, 2 -> "Winter";
      case 6, 7, 8 -> "Summer";
      default -> "Spring or Autumn";
    };
    System.out.println(season);
  }
}
Running it with --source 11 fails, because switch expressions did not exist in Java 11 -
java --source 11 Season.java
 OUTPUT of the above command
Season.java:5: error: switch expressions are not supported in -source 11
    String season = switch (month) {
                    ^
  (use -source 14 or higher to enable switch expressions)
Season.java:6: error: multiple case labels are not supported in -source 11
      case 12, 1, 2 -> "Winter";
             ^
  (use -source 14 or higher to enable multiple case labels)
Season.java:6: error: switch rules are not supported in -source 11
      case 12, 1, 2 -> "Winter";
                    ^
  (use -source 14 or higher to enable switch rules)
3 errors
error: compilation failed
Note - The --source option also switches the launcher into source-file mode, so it can be used to run a file whose name does not end in .java. We will use this for shebang scripts below. Using libraries from the class path A single-file program can use classes from libraries in the usual way, by putting the library's JAR files or class directories on the class path with -cp. In the example below, TextUtils stands for a library class, which has already been compiled into the classes directory -
A compiled library class
//TextUtils.java
public class TextUtils {
  public static String banner(String text) {
    String line = "*".repeat(text.length() + 4);
    return line + "\n* " + text + " *\n" + line;
  }
}
A single-file program which uses the library
//UsesLibrary.java
public class UsesLibrary {
  public static void main(String[] args) {
    System.out.println(TextUtils.banner("Java 11"));
  }
}
java -cp classes UsesLibrary.java
 OUTPUT of the above command
***********
* Java 11 *
***********
Shebang scripts on Linux and macOS On Unix-like systems, a single-file Java program can be turned into an executable script, just like a shell or Python script. The first line of the file is a shebang line, which tells the operating system to run the file with java. The launcher ignores this first line when it compiles the file -
A Java shebang script (file name: greet, no extension)
#!/usr/bin/java --source 11
public class Greet {
  public static void main(String[] args) {
    for (String name : args) {
      System.out.println("Hello, " + name + "!");
    }
  }
}
chmod +x greet
./greet Tom Harry
Hello, Tom!
Hello, Harry!
  • The script file must not have a .java extension. A .java file is expected to be a valid Java source file, which a shebang line is not. Since the name does not end in .java, --source is needed in the shebang line to put the launcher in source-file mode.
  • Use the path of the java executable on your system in the shebang line. You can find it with which java.
Limitations of source-file mode
  • One source file - in Java 11, the program must fit in a single file. Classes in other source files are not compiled, although already compiled classes can be used from the class path. (Java 22, through JEP 458, extended the launcher to compile other source files which the program uses.)
  • The first class is run - the first top-level class in the file must have the main method.
  • Compiled on every run - nothing is cached, so programs that are run often, or that need a fast startup, should be compiled ahead of time.
  • No annotation processing - the in-memory compiler does not run annotation processors, so libraries like Lombok do not work in source-file mode.
Summary In this tutorial we saw how Java 11 can run a program directly from its source file with the java command, which compiles it in memory without writing .class files. We passed arguments to the program, put several classes in one file, compiled for an older Java version with --source, and used library classes from the class path. We then turned a Java program into a shebang script, and lastly went through the limitations of source-file mode. In the next tutorial, we will look at the other API additions in Java 11Click to read tutorial: Java 11 API Additions: Files.readString, Predicate.not, such as Files.readString() and Predicate.not().