Your First Program (Compact Source Files)

Write a runnable Java 25 program with just a void main() method, and know what a compact source file gives you and when to switch to a normal class.

Syntaxvoid main() { IO.println("Hello"); } // or, with arguments void main(String[] args) { ... }

Since Java 25 a program can be a single file with no class declaration, no static and no public. This is a compact source file (finalised by JEP 512 after four preview rounds):

void main() {
    IO.println("Hello, back end");
}

Save it as Hello.java and run java Hello.java. The launcher compiles it in memory and runs it.

What the compiler does for you

  • It wraps everything in the file in an implicitly declared class named after the file. Fields and methods at the top of the file become members of that class, so main can call them directly.
  • It imports the whole java.base module, so List, Map, Path, Files, Duration, BigDecimal and the rest work without import lines. Other modules still need imports: java.net.http.HttpClient is in java.net.http, not java.base.
  • main can be an instance method with no parameters. Use void main(String[] args) when you need command-line arguments.

IO is a class in java.lang (new in Java 25) with println, print and readln. In the final version you write IO.println; the bare println from the preview releases no longer compiles.

Records, enums and classes in the same file

You can declare records, enums, interfaces and classes alongside main. They become nested types of the implicit class, which is plenty for a script, a migration helper or a quick prototype of an API's data model.

Limits worth knowing

  • The implicit class has no name you can use in code, so other classes cannot call into it. It is a program, not a library.
  • It cannot be in a named package, and it cannot declare a constructor.
  • It is the same language underneath. When the file grows, wrap the contents in class Name { ... }, add the imports and carry on. Nothing else changes.

Production services are still built from normal classes in packages, with a build tool. Compact source files are for learning, scripts and one-off tools, and they are what most examples on this site use because they keep the focus on the code that matters.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Write a one-file tool that loads a CSV export and prints a summary without setting up a build.
  • Prototype the records for a new API's requests and responses before moving them into a project.
  • Script a quick check against a staging endpoint during an incident, run with java Check.java.

More examples

Fields and helper methods are members of the implicit class

Because main is an instance method, it can read the requests field and call handle() without static. In a classic class the same code would need every member marked static or an object created first.

Example · java
int requests = 0;

String handle(String path) {
    requests++;
    return switch (path) {
        case "/health" -> "200 OK";
        case "/users" -> "200 OK";
        default -> "404 Not Found";
    };
}

void main() {
    IO.println("/health " + handle("/health"));
    IO.println("/admin " + handle("/admin"));
    IO.println("requests handled: " + requests);
}

// Output:
// /health 200 OK
// /admin 404 Not Found
// requests handled: 2

Model data with records and enums in the same file

Records and enums declared above main are nested types of the implicit class. A TreeMap keeps the enum keys in declaration order, so the output is stable.

Example · java
enum Role { ADMIN, USER }

record User(String name, Role role) {}

void main() {
    var users = List.of(
        new User("ada", Role.ADMIN),
        new User("lin", Role.USER),
        new User("sam", Role.USER));

    Map<Role, List<String>> byRole = new TreeMap<>();
    for (User user : users) {
        byRole.computeIfAbsent(user.role(), r -> new ArrayList<>()).add(user.name());
    }
    IO.println(byRole);
    IO.println(users.getFirst());
}

// Output:
// {ADMIN=[ada], USER=[lin, sam]}
// User[name=ada, role=ADMIN]

java.base is imported, other modules are not

Everything used here except HttpClient lives in java.base, which a compact source file imports automatically. Remove the import line and the file fails to compile with cannot find symbol.

Example · java
import java.net.http.HttpClient; // java.net.http is a separate module

void main() throws IOException {
    // Files, Path, Properties and Duration come from java.base: no imports needed.
    Path file = Files.createTempFile("app", ".properties");
    Files.writeString(file, "port=8080\nenv=dev\n");

    var props = new Properties();
    try (var reader = Files.newBufferedReader(file)) {
        props.load(reader);
    }
    IO.println("port = " + props.getProperty("port"));
    IO.println("timeout = " + Duration.ofSeconds(30));
    try (HttpClient client = HttpClient.newHttpClient()) { // AutoCloseable since Java 21
        IO.println("client version = " + client.version());
    }

    Files.delete(file);
}

// Output:
// port = 8080
// timeout = PT30S
// client version = HTTP_2

Discussion

  • Be the first to comment on this lesson.