Part 12 of 15

Unnamed Classes and Instance Main Methods (JEP 445): Java for Scripts and Beginners

Preview Feature — Requires --enable-preview at compile and runtime.

The Boilerplate Problem

Teaching Java to a beginner means explaining class declarations, access modifiers, static, and String[] before they can print “Hello, World!”:

// Traditional Java — 4 concepts before printing one line
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

Every word carries meaning, but that meaning is irrelevant until the student understands OOP, the JVM class model, and program entry points. JEP 445 removes this barrier.


Instance Main Methods

Any of these signatures is now a valid entry point:

// Simplest — no class, no static, no args
void main() {
    System.out.println("Hello, World!");
}
// With args — still no class, no static
void main(String[] args) {
    System.out.println("Args: " + args.length);
}
// Static still works — backward compatible
static void main(String[] args) {
    System.out.println("Classic entry point");
}

The JVM’s launch protocol now tries entry points in this priority order:

  1. static void main(String[]) — traditional (highest priority)
  2. static void main() — static, no args
  3. void main(String[]) — instance method with args
  4. void main() — instance method, no args (lowest priority)

Unnamed Classes

Methods and fields declared at the top level of a source file (outside any class) belong to an unnamed class — implicitly created by the compiler:

// Greeting.java — no class declaration needed
String greeting = "Hello";

void main() {
    System.out.println(greeting + ", World!");
    printVersion();
}

void printVersion() {
    System.out.println("Running on Java " + Runtime.version().feature());
}

The compiler wraps this in an implicit final class named after the source file. The class:

  • Is final — cannot be subclassed
  • Extends Object implicitly
  • Cannot implement interfaces explicitly
  • Has no declared constructors (uses the default)

What’s Allowed in an Unnamed Class

// Fields — instance or static
int count = 0;
static final String VERSION = "1.0";

// Methods — instance or static
void process(String input) { ... }
static String format(double value) { ... }

// Inner types — classes, interfaces, enums, records
record Point(int x, int y) {}

// Import statements — still required
import java.util.List;
import java.time.Instant;

// main — required (at least one of the valid signatures)
void main() {
    var points = List.of(new Point(1, 2), new Point(3, 4));
    points.forEach(p -> System.out.println(p));
}

Compiling and Running

# Compile with preview
javac --enable-preview --release 21 Hello.java

# Run with preview
java --enable-preview Hello

Source-file launch (JEP 330, Java 11+) also works:

# Run directly without separate compile step (source launcher)
java --enable-preview --source 21 Hello.java

Practical Use Cases

Quick Utility Script

// ConvertCsv.java
import java.nio.file.*;
import java.util.*;

void main(String[] args) throws Exception {
    if (args.length != 2) {
        System.err.println("Usage: ConvertCsv <input.csv> <output.json>");
        System.exit(1);
    }
    var lines = Files.readAllLines(Path.of(args[0]));
    var headers = Arrays.asList(lines.get(0).split(","));

    var json = new StringBuilder("[");
    for (int i = 1; i < lines.size(); i++) {
        var values = lines.get(i).split(",");
        json.append("\n  {");
        for (int j = 0; j < headers.size(); j++) {
            json.append("\"%s\": \"%s\"".formatted(headers.get(j), values[j]));
            if (j < headers.size() - 1) json.append(", ");
        }
        json.append(i < lines.size() - 1 ? "}," : "}");
    }
    json.append("\n]");
    Files.writeString(Path.of(args[1]), json.toString());
    System.out.println("Converted " + (lines.size() - 1) + " rows.");
}

Run: java --enable-preview --source 21 ConvertCsv.java input.csv output.json

Learning / Teaching

// Step 1: student writes this
void main() {
    System.out.println("Hello!");
}

// Step 2: add variables
String name = "Alice";
void main() {
    System.out.println("Hello, " + name + "!");
}

// Step 3: add methods
String greet(String name) {
    return "Hello, " + name + "!";
}
void main() {
    System.out.println(greet("Alice"));
}

// Step 4: wrap in a class when needed — transition is natural
public class Greeter {
    String greet(String name) {
        return "Hello, " + name + "!";
    }
    public static void main(String[] args) {
        System.out.println(new Greeter().greet("Alice"));
    }
}

The student learns concepts incrementally — variables, methods, then classes — without being forced to understand all of OOP on day one.


Interaction with Regular Classes

An unnamed class lives in the default package and cannot be referenced by name from other classes (it has no accessible name). It can use any other class normally:

// Uses a regular named class
import com.example.OrderService;

var service = new OrderService();

void main() {
    service.processAllPending();
    System.out.println("Done");
}

Limitations

  • Cannot implement an interface or extend a class explicitly
  • Cannot be instantiated from another class (no accessible name)
  • Not suitable for library code, only for executable entry points
  • this refers to the unnamed class instance — usable but rarely needed

Key Takeaways

  • Methods declared at the top level of a .java file belong to an implicit unnamed class — no class declaration needed
  • void main() (no static, no String[]) is a valid entry point — the JVM tries instance methods if no static main is found
  • The compiler wraps top-level code in a final unnamed class extending Object
  • Supports fields, methods, and inner type declarations at the top level
  • Unnamed classes cannot implement interfaces or be referenced by name
  • Primary use cases: scripts, teaching, rapid prototyping — not library or application code
  • Requires --enable-preview in Java 21

Next: Foreign Function & Memory API (JEP 442) — call native C functions and manage off-heap memory without JNI.