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.
void 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
maincan call them directly. - It imports the whole
java.basemodule, soList,Map,Path,Files,Duration,BigDecimaland the rest work without import lines. Other modules still need imports:java.net.http.HttpClientis injava.net.http, notjava.base. maincan be an instance method with no parameters. Usevoid 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
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.
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: 2Model 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.
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.
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