simpler programs
compact source files, instance main, and import module.
- chapter
- chapter 08.08
- reading time
- 7 min read
- java 25+
since java 25, void main() { IO.println("hi"); } in a file is a complete program. no class, no static, no String[] args, no System.out. it's a compact source file (JEP 512): fields and methods with no class around them, wrapped by the compiler in an implicit final class. its main can be an instance method, every compact file gets import module java.base for free (JEP 511), and the new java.lang.IO class does console input and output.
it's the same language, with the ceremony made optional. nothing is a toy mode.
the picture
you know how the first java program anyone sees is public static void main(String[] args), and the teacher says "ignore all that for now"? that's five concepts (access modifiers, classes, static, arrays, command-line arguments) standing between a beginner and printing one line. java 25 lets you add them when you need them instead of before you start.
think of a note to yourself versus a formal letter. the formal letter has a letterhead, an address block, a date, "dear sir or madam". the note just says "buy milk". both are written in the same language, and a note can always grow into a letter. a compact source file is the note: when the program grows, you add a class declaration and nothing else has to change.
- 1String greeting = "hi";
- 2
- 3void main() {
- 4 IO.println(greeting + ", " + List.of("ada", "bob"));
- 5}
a compact source file: a field and a method, and nothing around them. no class, no static, no String[] args, no import for List. java 25 compiles and runs this as is.
javap -p on the real class file from javac 25.String greeting = "hi";
void main() {
IO.println(greeting + ", " + List.of("ada", "bob"));
}hi, [ada, bob]
java Hello.java compiles it in memory and runs it, no javac step (that's the source launcher, java 11; since java 22 it also finds other .java files your program uses). everything on this site that says run is either a file like this one or a classic class.
which main runs
the launcher used to accept one shape only: public, static, void, named main, taking a String[]. now it accepts four, and picks with two rules. first, a main that takes String[] beats one that takes nothing, static or not. second, if the chosen one is an instance method, the class needs a non-private no-argument constructor: the launcher makes an object and calls main on it.
- 1class Main {
- 2 static void main() { IO.println("B"); }
- 3 void main(String[] args) { IO.println("C"); }← java runs this one
- 4}
a main that takes String[] always wins, static or not. it's an instance method, so the launcher first calls the no-argument constructor, then main(String[] args) on that new object.
java picks, or why javac won't even get that far. all 15 combinations were compiled and launched on jdk 25.instance main works in any class, not just compact files. that's what removes the classic beginner trap of fields that "have to be static because main is static":
class Main {
int count = 0;
void main() {
count++;
IO.println("instance main in an ordinary class, count = " + count);
}
}instance main in an ordinary class, count = 1
class Main {
static void main() { IO.println("static main()"); }
void main(String[] args) { IO.println("instance main(String[])"); }
}import module
import module M; imports every public top-level type in every package that module M exports. compact source files get import module java.base; implicitly, but you can write it in any file, modular or not:
import module java.base; // 58 packages: java.util, java.io, java.nio.file, java.time...
class Report {
List<String> lines = new ArrayList<>();
Map<String, Path> files = new HashMap<>();
Duration timeout = Duration.ofSeconds(5);
}the catch is names that exist in two packages. java.sql has a Date, and so does java.util:
import module java.base;
import module java.sql;
class Amb {
Date d;
}error: reference to Date is ambiguous
both class java.sql.Date in java.sql and class java.util.Date in java.util matcha single-type import settles it: add import java.util.Date;. the precedence is the obvious one, most specific wins: single-type imports beat package (.*) imports, which beat module imports.
| import | brings in | wins over |
|---|---|---|
import java.util.Date; | one type | everything below |
import java.util.*; | one package | module imports |
import module java.base; | every package the module exports | nothing |
IO: the console, finally simple
java.lang.IO is five static methods: print(Object), println(Object), println(), readln() and readln(String prompt). it's in java.lang, so it's always there, in every file. (in the last preview its methods were auto-imported into compact files, so you could write bare println. the final version dropped that: you write IO.println.)
void main() {
String name = IO.readln("name? ");
int n = Integer.parseInt(IO.readln("how many? "));
for (int i = 0; i < n; i++) IO.println("hi " + name);
}$ java Ask.java
name? ada
how many? 2
hi ada
hi adano Scanner, no BufferedReader wrapped around System.in, no throws IOException. IO writes to System.out and reads from System.in, so it plays nicely with redirection and pipes.
four previews, three names+
this took a while to get right. JEP 445 previewed "unnamed classes and instance main methods" in java 21. JEP 463 (22) renamed them "implicitly declared classes" and simplified the launch rules. JEP 477 (23) added the IO class with auto-imported methods and the implicit import module java.base, and JEP 495 (24) renamed them "simple source files". JEP 512 finalized it all in java 25 as "compact source files", moving IO into java.lang and dropping the auto-imported methods. module imports went through JEP 476 (23) and 494 (24) before JEP 511 made them final, also in 25. the whole journey is on the release timeline.
void main() { IO.println("hi"); }is a whole java 25 program.- a compact source file is wrapped in an implicit final class you can't refer to by name.
maincan be an instance method. one that takesString[]beats one that doesn't.import module java.base;is implicit in compact files and allowed in any file.- ambiguous names from module imports are fixed with a single-type import.
IO.printlnandIO.readlnreplaceSystem.outandScannerfor simple console programs.