You write a throwaway script or a first-week demo. Java asks for a public class, a public static void main(String[] args), and System.out.println — three programming-in-the-large concepts before you have printed a line.
Teams copy the ceremony anyway. Instructors say “ignore that for now.” The noise stays.
Compact source files let you put fields and methods at the top of a .java file without a class shell. The compiler wraps them in an implicit class. Pair that with instance main methods — void main() is enough — and the new java.lang.IO helpers, and a Hello, World fits on three lines.
Reach for this when the program is a single small entry point — scripts, demos, kata files. Grow it into an ordinary class when you need a named type other code can import.
The problem the old entry point created
Classic Hello, World:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Four concepts land before the useful work:
class+public— encapsulation for multi-file systems, not a one-file scriptstatic— forces morestatichelpers, or an early dive into objectsString[] args— shell wiring you may never readSystem.out.println— a chain beginners cannot yet explain
Compact source files and instance main postpone those until they earn their keep.
When compact source files shipped
| Release | Status | Spec |
|---|---|---|
| Java 21 | Preview (“Unnamed Classes and Instance Main Methods”) | JEP 445 |
| Java 22 | Second preview | JEP 463 |
| Java 23 | Third preview | JEP 477 |
| Java 24 | Fourth preview (“Simple Source Files…”) | JEP 495 |
| Java 25 LTS | Standard feature (“Compact Source Files…”) | JEP 512 |
Use Java 25+. No --enable-preview. Finalization also moved IO into java.lang and stopped auto-static-importing its methods — call IO.println(...) (or import the methods yourself).
Mental model: implicit class, launchable main
A compact source file is a .java file whose top-level fields and methods are not wrapped in an explicit class. The compiler treats them as members of an implicitly declared final class in the unnamed package:
| Property | What you get |
|---|---|
| Name | Implementation-specific — you cannot use it in source (no new ThatName()) |
| Constructors | Default no-arg only |
| Inheritance | Extends Object; no interfaces |
| Requirement | Must declare a launchable main |
A launchable main is any accessible void main the launcher will pick:
- Prefer
main(String[] args)if present - Else
main()with no parameters - If that method is
static, invoke it; if it is an instance method, construct with the no-arg constructor, then callmainon the instance
public and static are optional. String[] args is optional.
Lab: Hello without the shell
HelloWorld.java:
void main() {
IO.println("Hello, World!");
}
Run with the source-code launcher:
java HelloWorld.java
Hello, World!
Or compile then run:
javac HelloWorld.java
java HelloWorld
Same idea with helpers nearby — they are instance members of the implicit class:
String greeting() { return "Hello, World!"; }
void main() {
IO.println(greeting());
}
Or a field:
String greeting = "Hello, World!";
void main() {
IO.println(greeting);
}
Lab: instance main inside an ordinary class
You do not need a compact file to drop public static and String[]:
class HelloWorld {
void main() {
IO.println("Hello, World!");
}
}
java HelloWorld.java
Useful when you already want a named class, but still want a quieter entry point.
Lab: console I/O with java.lang.IO
IO lives in java.lang (implicitly imported everywhere — not only in compact files). Static methods are not auto-imported; name the class:
| Method | Role |
|---|---|
IO.print(Object) | Write, no newline |
IO.println(Object) / IO.println() | Write + newline |
IO.readln(String) / IO.readln() | Prompt (optional) + read a line |
void main() {
String name = IO.readln("Please enter your name: ");
IO.print("Pleased to meet you, ");
IO.println(name);
}
No BufferedReader, no InputStreamReader, no checked-exception theater for a first interactive program.
Lab: automatic java.base imports
Every compact source file behaves as if it started with:
import module java.base;
So types from packages exported by java.base (List, Map, Stream, Path, …) work without explicit imports:
void main() {
var authors = List.of("James", "Bill", "Guy", "Alex", "Dan", "Gavin");
for (var name : authors) {
IO.println(name + ": " + name.length());
}
}
Ordinary source files do not get this automatic module import — wrap the members in a class and add import module java.base; (or specific imports) when you graduate the file.
Growing out of compact form
Compact files are an on-ramp, not a parallel dialect. Wrap the members in a class and keep main as-is:
import module java.base;
class NameLengths {
void main() {
var authors = List.of("James", "Bill", "Guy", "Alex", "Dan", "Gavin");
for (var name : authors) {
IO.println(name + ": " + name.length());
}
}
}
Later you can add public, static, packages, modules, and a conventional main(String[] args) when other components need them.
What you still cannot do
| Allowed in a compact file | Not how this feature works |
|---|---|
| Fields, methods, nested types as members | Refer to the implicit class by name |
this and instance method refs | new the implicit class from source |
Launch with java File.java or javac + java | Treat it as a library type other packages import |
Explicit import / import module as needed | Skip main — compile error without a launchable entry point |
Note: Multiple compact files can live in the unnamed package — each is its own program with its own main, not one shared implicit type.
When they win vs when to skip
| Need | Prefer |
|---|---|
| Script, kata, demo, REPL-ish one-file tool | Compact source + void main() + IO |
| Named type reused by other classes | Ordinary class / record / interface |
| Shared library on the module path | Explicit packages + module descriptor |
| Production service entry | Keep a clear, documented main; compact form is optional sugar |
Do not ship a multi-file architecture as a pile of nameless compact programs. The feature removes ceremony for small programs; it does not replace packages.
Gotchas
IO.println needs the qualifier
At finalization, static IO methods are not implicitly imported into compact files. Write IO.println(...), or import static java.lang.IO.println;.
Launcher preference for main(String[])
If both main(String[] args) and main() exist, the launcher prefers the String[] form. Keep one launchable entry point unless you intend that rule.
Implicit class name is not an API
The compiler may emit HelloWorld.class from HelloWorld.java, but that name is an implementation detail for tooling — do not design around referencing it from other source files.
Automatic imports are compact-only
Moving members into an explicit class drops the automatic import module java.base. Add the import (or specific imports) in the same edit so the file still compiles.
Not a substitute for records or sealed types
Compact files shrink the program shell. For data carriers and closed hierarchies, keep using records and sealed + pattern switch.
Cheat sheet
// HelloWorld.java (compact)
void main() {
IO.println("Hello, World!");
}
java HelloWorld.java # source launcher
javac HelloWorld.java && java HelloWorld
Launchable main: void main() or void main(String[])
— public/static optional; instance → new + invoke
Compact file = implicit final class (unnamed package)
— must have launchable main; no usable type name
IO in java.lang: print / println / readln
Compact files auto: import module java.base;
Java 25+ (JEP 512); no preview flag
Do:
- Use compact files for scripts, teaching steps, and throwaway prototypes.
- Prefer
IO.println/IO.readlnfor simple console I/O. - Grow by wrapping members in an explicit
classwhen other code must name the type.
Don’t:
- Expect to
newor import the implicit class from other source. - Forget that ordinary classes do not auto-import
java.base. - Replace a real module layout with a folder of nameless compact entry points.
Pros and cons
Pros
- Ceremony drops for first programs and one-file utilities
- Same language and toolchain —
javac/java, no dialect - Smooth growth path: wrap in a class, keep
main - Final in Java 25 — no preview tax
Cons
- Implicit class is not a reusable library type
- Automatic
java.baseimport only in compact files (easy surprise when wrapping) - Temptation to keep growing a “temporary” script past its useful life
- Requires Java 25+ for the standard feature
Wrap-up
Compact source files and instance main methods (JEP 512) let small programs start with void main() and IO.println, then expand into ordinary classes without rewriting the entry point. Use them when the file is the program. When you need a named type for other packages, wrap the members and keep climbing the same language.
It sits with Scoped Values and Flexible Constructor Bodies as Java 25 language work. Compact files also start with an implicit import module java.base — the next post is how to get that reach in an ordinary class.