Java 25: Compact Source Files and Instance Main Methods (JEP 512)

Java 25 finalizes JEP 512, which makes small programs far less noisy: you can write a main as an instance method (no static required) and you can write compact source files — source files that implicitly declare a class for any top-level fields and methods. A light-weight java.lang.IO helper is available for beginner-friendly console I/O, and compact source files automatically gain access to java.base classes to reduce imports.

1. Intro — why this matters

For decades the first Java program looked like a barrier:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

That ceremony taught important concepts, but it also made the very first step into Java harder than necessary. JEP 512 gives a safe, compatible on-ramp: small programs can now be written with far less boilerplate while still being ordinary Java and fully upgradable into “real” Java classes as needed. This change is finalized in JDK 25.


2. What changed (short list)

  • Compact source files: a source file that contains top-level fields and methods (not inside a class) is treated as implicitly declaring a final top-level class whose members are those fields/methods. The class is unnamed and lives in the unnamed package/module. The compiler enforces that the file must have a launchable main.
  • Instance main methods: main may be declared as an instance method (for example void main() or void main(String[] args)); when needed the launcher will instantiate the class and invoke the instance main. This reduces the mental load of public static void main(String[] args).
  • java.lang.IO helper: a small console I/O helper (methods like IO.println(...) and IO.readln(...)) is provided in java.lang for convenience and is therefore available without imports. Its implementation uses System.out/System.in. Note: the static methods are not implicitly statically imported into compact source files — you call them as IO.println(...).
  • Automatic java.base import in compact files: compact source files act as if the entire java.base module were imported on demand — common classes like List, Map, BigInteger, etc., are directly usable without an explicit import.

2. Compact Source Files

Before Java 25, every program must be inside a class, and the entry point was always:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

This was verbose for small demos or scripts.

👉 With compact source files, you can now write top-level methods and fields directly in a .java file. The compiler automatically wraps them inside an implicit final class behind the scenes.

Example 1: Hello World in a compact source file

// Hello.java
void main() {
    IO.println("Hello, World!");
}

Run it directly:

java Hello.java

Output:

Hello, World!

➡️ No class, no static, no public. Just a simple entry point.

Example 2: Using fields and helper methods

You can declare fields and helper methods at the top level. They all become members of the implicit class.

// Greet.java
String greeting = "Welcome to Java 25!";

String shout(String text) {
    return text.toUpperCase();
}

void main() {
    IO.println(shout(greeting));
}

Run:

java Greet.java

Output:

WELCOME TO JAVA 25!

➡️ The compiler internally creates an unnamed class with greeting and shout as members.

3. Instance main Methods

Traditionally, Java only supported:

public static void main(String[] args)

With Java 25, you can now declare main as an instance method.

Example 3: Instance main without arguments

// Greeter.java
class Greeter {
    void main() {
        IO.println("Hello from an instance main!");
    }
}

Run:

java Greeter.java

Output:

Hello from an instance main!

➡️ The launcher automatically creates an object of Greeter and invokes its main.

Example 4: Instance main with arguments

// Echo.java
class Echo {
    void main(String[] args) {
        for (var arg : args) {
            IO.println("Arg: " + arg);
        }
    }
}

Run:

java Echo.java one two three

Output:

Arg: one
Arg: two
Arg: three

➡️ Both static and instance forms are supported:

  • void main()
  • void main(String[] args)

4. IO Console Helper

To make small programs even simpler, Java 25 introduces java.lang.IO, a helper class for console input/output.

  • Before Java 25: System.out.println("Hello, Java!");
  • Now: IO.println("Hello, Java!");

Example 5: Using IO helper

void main() {
    IO.println("Enter your name: ");
    String name = IO.readln();
    IO.println("Hello, " + name + "!");
}

Run and interact:

Enter your name: 
Ashish
Hello, Ashish!

➡️ No need to remember System.out or set up a Scanner.

5. Automatic Imports

In compact source files, common Java APIs from java.base (like List, Map, BigInteger) are automatically available without import.

// Numbers.java
void main() {
    var numbers = List.of(10, 20, 30);
    IO.println("Numbers: " + numbers);
}

No import java.util.List; needed.

6. How Compact Source Evolves into a Class

Compact source files are not a new language, they’re just a shortcut.
You can always “grow” them into normal Java classes.

Compact form:

void main() {
    IO.println("Quick start in Java 25!");
}

Expanded form:

class QuickStart {
    void main() {
        IO.println("Quick start in Java 25!");
    }
}

This ensures seamless transition from beginner scripts to full OOP programs.

7. Important gotchas & notes

  • Implicit class is unnamed and final — the compiler generates a name you must not rely on in source code; you cannot new the implicitly-declared class from within the file. It’s meant as an entry-point, not a reusable API.
  • IO helper methods not implicitly imported as bare names — you must call IO.println(...) (they live in java.lang so no import is needed, but static implicit import into compact files was intentionally removed).
  • No statements outside methods — compact source files still require methods (you cannot write top-level statements that are executed as a script body; the design requires methods and fields so programs can grow).
  • Launcher lookup rules — if a classic public static void main(String[]) exists it retains priority; instance main paths are fallbacks or alternatives. See the JEP/JVM spec for exact lookup semantics.

8. Compatibility & tooling

  • JEP 512 was finalized for JDK 25; tooling (IDE support, linters) are already adding support (IntelliJ and other vendors published guidance shortly after the release). If your build tools or CI assume public static void main always exists, you may want to update them to accept instance mains or use explicit javac + java steps.

9. Summary of Changes (JEP 512)

FeatureBefore Java 25Java 25+ (JEP 512)
Entry point declarationpublic static void main(String[] args)void main() or void main(String[] args) (static or instance)
Class requirementExplicit public class ...Implicit class (no explicit declaration)
Console I/OSystem.out.println with importsIO.println (available without import)
Import statementsManual for core APIsAuto-import for java.base in compact files

👉 Read more:

Backend developer working with Java, Spring Boot, Microservices, NoSQL, and AWS. I love sharing knowledge, practical tips, and clean code practices to help others build scalable applications.

Leave a Reply

Your email address will not be published. Required fields are marked *