Java 22: Run Multi-File Programs Without Compiling

Introduction Java 11Click to read: Java 11 let the java command run a program straight from a .java file, compiling it in memory - but only if the whole program fitted in that one file. As soon as a small program grew a second file, you were back to javac and a build setup. Java 22Click to read tutorial: Overview of New Features in Java 22 (JEP 458) removed that limit: the launcher can now run a program made of several source files, compiling the other files the program uses on demand. This tutorial explains how to launch multi-file source-code programs with examples - several classes in several files, programs in packages, using libraries, and when it is time to move to a build tool. A program in two files Here is a small program split over two files in the same directory: Main.java, with the main method, and Invoice.java, a record it uses -
Main.java
//Main.java
import java.util.List;

public class Main {
  public static void main(String[] args) {
    List<Invoice> invoices = List.of(new Invoice("INV-1", 1200.0), new Invoice("INV-2", 350.5));
    double total = invoices.stream().mapToDouble(Invoice::amount).sum();
    invoices.forEach(i -> System.out.println(i.describe()));
    System.out.printf("Total: %.2f%n", total);
  }
}
Invoice.java
//Invoice.java
public record Invoice(String number, double amount) {
  String describe() {
    return number + " for " + amount;
  }
}
Running the file containing main is enough - no javac, no class files on disk -
java Main.java
 OUTPUT of the above command
INV-1 for 1200.0
INV-2 for 350.5
Total: 1550.50
How it works
  • The launcher compiles Main.java in memory. When the program refers to a class it cannot find - here Invoice - the launcher looks for its source file in the directory tree of the launched file, Invoice.java, and compiles it too.
  • Files are compiled lazily, when a class is first needed while the program runs, so source files in the directory which the program never uses are never compiled. A compile error in a file is reported when that file is first needed.
  • As in Java 11, nothing is written to disk, and the program is recompiled every time it runs.
Programs in packages If the launched file declares a package, the launcher works out the root of the source tree from the package name and the file's location, and finds other classes relative to it - exactly as javac would with a source path -
src/com/shop/App.java
//App.java
package com.shop;

import com.shop.model.Order;

public class App {
  public static void main(String[] args) {
    Order order = new Order("Laptop", 2);
    System.out.println("Order: " + order + ", packaged in " + Order.class.getPackageName());
  }
}
src/com/shop/model/Order.java
//Order.java
package com.shop.model;

public record Order(String item, int quantity) { }
java src/com/shop/App.java
 OUTPUT of the above command
Order: Order[item=Laptop, quantity=2], packaged in com.shop.model
Because App.java declares package com.shop and lives in src/com/shop, the launcher takes src as the source root and finds com.shop.model.Order in src/com/shop/model/Order.java. Using libraries A multi-file source program can use libraries in the usual way, by putting their JAR files on the class path. The --class-path option accepts a wildcard, so all JARs in a directory can be added at once -
java --class-path "lib/*" Main.java
The program can also be modular: if the source root contains a module-info.java, the program is compiled and run as a named module, with libraries on --module-path.
When to move to a build tool Launching from source is ideal for learning, scripts, prototypes and small utilities - the "grow your program gradually" path the feature was designed for, together with the compact source files of Java 25Click to read tutorial: Java 25 Compact Source Files and Instance main Methods. Move to javac and a build tool such as Maven or Gradle when the program needs to -
  • download and manage dependencies,
  • run tests, use annotation processors (which the source launcher does not run), or produce a JAR to distribute, or
  • start quickly every time, since the source launcher recompiles on each run.
Summary In this tutorial we saw how Java 22 extended the source-code launcher to programs made of several files. We ran a two-file program with java Main.java, saw that the launcher compiles the other files lazily, in memory, when their classes are first needed, and ran a program in packages, where the source root is worked out from the package name. Lastly, we added libraries with --class-path and looked at when to switch to a build tool. Next, read the overview of new features in Java 22Click to read tutorial: Overview of New Features in Java 22.