Unnamed Classes and Instance Main Methods (JEP 445): Java for Scripts and Beginners
Preview Feature — Requires
--enable-previewat 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:
static void main(String[])— traditional (highest priority)static void main()— static, no argsvoid main(String[])— instance method with argsvoid 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
Objectimplicitly - 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
implementan interface orextenda class explicitly - Cannot be instantiated from another class (no accessible name)
- Not suitable for library code, only for executable entry points
thisrefers to the unnamed class instance — usable but rarely needed
Key Takeaways
- Methods declared at the top level of a
.javafile belong to an implicit unnamed class — noclassdeclaration needed void main()(nostatic, noString[]) is a valid entry point — the JVM tries instance methods if no staticmainis found- The compiler wraps top-level code in a
finalunnamed class extendingObject - 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-previewin Java 21
Next: Foreign Function & Memory API (JEP 442) — call native C functions and manage off-heap memory without JNI.