If you have ever watched a new developer’s face when they see their first Java program, you know the problem. Before they can print “Hello, World!”, they must understand public, class, static, void, String[], and why the file name must match the class name. Compare that to Python, where the entire program is print("Hello, World!"). That gap has been a legitimate criticism of Java for over two decades.
Java 25 fixes this with two finalized features that, when combined, make Java programs as simple as scripts while retaining the full power of the platform:
| Feature | JEP | Status in Java 25 | What It Does |
|---|---|---|---|
| Module Import Declarations | JEP 476 | Final | Import all public types from a module with one statement |
| Implicitly Declared Classes & Instance Main Methods | JEP 477 | Final | Write Java programs without class declarations or public static void main |
These features went through multiple rounds of preview in Java 22, 23, and 24. In Java 25, they are finalized and production-ready. No --enable-preview flag required. This post covers both features in detail — what they are, how they work, where the edge cases are, and when you should (and should not) use them.
Think of it this way: Java has always been a language that favors ceremony — explicit declarations that make large codebases maintainable. These features do not remove that ceremony from production code. Instead, they give you a casual mode for situations where the ceremony gets in the way: teaching, scripting, prototyping, and quick experiments.
Before we get to the simplified programs, we need to tackle the import problem. Java’s import system works well once you know it, but it creates a wall of boilerplate at the top of every file. A typical utility class might start like this:
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.nio.file.Path;
import java.nio.file.Files;
public class DataProcessor {
// ... actual code starts here, 14 lines later
}
That is fourteen import statements before a single line of business logic. IDEs hide them behind a fold, but they still exist, and they are still noise. Star imports (import java.util.*) help, but they only import from a single package, not from related packages. You still need separate star imports for java.util, java.util.stream, java.io, and java.nio.file.
Module import declarations solve this at the module level.
The new syntax imports all public top-level types exported by a module:
import module java.base;
That single line replaces every import you would ever need from the java.base module. It imports types from java.util, java.util.stream, java.io, java.nio.file, java.time, java.math, java.net, java.text, java.util.concurrent, and every other package in the java.base module — roughly 60 packages and over 1,500 types.
Here is the comparison side by side:
// BEFORE: Traditional imports
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.stream.Collectors;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.nio.file.Path;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class DataProcessor {
public void process() throws IOException {
Path file = Path.of("data.csv");
List lines = Files.readAllLines(file);
Map totals = new HashMap<>();
LocalDateTime now = LocalDateTime.now();
// ...
}
}
// AFTER: Module import
import module java.base;
public class DataProcessor {
public void process() throws IOException {
Path file = Path.of("data.csv");
List lines = Files.readAllLines(file);
Map totals = new HashMap<>();
LocalDateTime now = LocalDateTime.now();
// ...
}
}
Fourteen imports collapsed to one. The code below the imports is identical — you do not change how you use the types, only how you import them.
You can use import module with any named module in the Java Platform Module System (JPMS). Here are the most commonly used ones:
| Module | Key Packages | Common Use Case |
|---|---|---|
java.base |
java.lang, java.util, java.io, java.nio, java.time, java.math, java.net, java.util.concurrent | Core language — covers 90% of typical imports |
java.sql |
java.sql, javax.sql | JDBC database access |
java.logging |
java.util.logging | JDK built-in logging |
java.net.http |
java.net.http | HTTP client (HttpClient, HttpRequest, HttpResponse) |
java.desktop |
java.awt, javax.swing | GUI applications |
java.xml |
javax.xml, org.xml.sax, org.w3c.dom | XML parsing and processing |
java.compiler |
javax.tools, javax.annotation.processing | Annotation processing and compilation |
For most application code, import module java.base; is all you need. If you do database work, add import module java.sql;. If you use the HTTP client, add import module java.net.http;. You can combine module imports with traditional imports freely.
A module import is not the same as importing every package with a star import. Here are the key rules:
1. Only public top-level types are imported. Nested classes, package-private classes, and non-exported packages are not imported. This is the same visibility you would get if you wrote individual import statements.
2. Transitive dependencies are included. If module A requires transitive module B, then import module A; also imports all exported types from module B. For example, java.sql requires transitive java.logging and java.xml, so importing java.sql gives you logging and XML types too.
3. No runtime cost. Module imports are purely a compile-time convenience. The compiled bytecode contains exactly the same class references as if you had written individual imports. There is no additional classloading, no additional memory usage, and no performance difference.
When you import an entire module, you inevitably import types with the same simple name from different packages. For example, java.base exports both java.util.List and java.awt.List (if you also import java.desktop). How does Java handle this?
The resolution follows a clear priority order:
| Priority | Import Type | Example | Wins When |
|---|---|---|---|
| 1 (highest) | Single-type import | import java.util.List; |
Always wins — most specific |
| 2 | Package star import | import java.util.*; |
Wins over module imports |
| 3 (lowest) | Module import | import module java.base; |
Only if no higher-priority import matches |
Here is a practical example:
// Scenario: Two modules export a class with the same simple name import module java.base; // exports java.util.List import module java.desktop; // exports java.awt.List // This would be ambiguous -- compiler error! // Listnames = new ArrayList<>(); // Which List? // Fix: Add a single-type import to disambiguate import java.util.List; // This takes priority over both module imports List names = new ArrayList<>(); // Resolves to java.util.List
The practical implication is simple: start with import module java.base;, and if the compiler reports an ambiguity, add a single-type import to resolve it. This is the exact same approach you use today when star imports clash — the resolution mechanism is familiar.
Within a single module, the JDK designers have already ensured there are no ambiguous simple names. You will only hit ambiguity when importing multiple modules that happen to export types with the same name, which is relatively rare in practice.
You can mix all three import styles in the same file:
import module java.base; // Module import -- all of java.base
import module java.sql; // Module import -- all of java.sql
import java.util.logging.*; // Star import -- one specific package
import com.myapp.util.StringUtils; // Single-type import -- one specific class
public class MyService {
// All types from java.base, java.sql, java.util.logging,
// and StringUtils are available here
}
This flexibility means you can adopt module imports gradually. Replace your most common star imports with a single import module java.base; and keep specific imports for third-party libraries that are not modularized.
To appreciate the difference, here are three real-world examples showing typical import blocks before and after module imports.
// BEFORE (22 imports)
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.stream.Collectors;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Path;
import java.nio.file.Files;
import java.util.function.Function;
import java.util.function.Predicate;
public class OrderService {
// ...
}
// AFTER (2 imports)
import module java.base;
import module java.net.http;
public class OrderService {
// Exact same code below -- all types resolve correctly
}
// BEFORE (12 imports)
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.List;
import java.util.ArrayList;
import java.util.Optional;
import java.time.LocalDateTime;
import java.time.Instant;
import java.math.BigDecimal;
import java.util.logging.Logger;
public class UserRepository {
// ...
}
// AFTER (2 imports)
import module java.base;
import module java.sql;
public class UserRepository {
// java.sql requires transitive java.logging,
// so Logger is available too
}
// BEFORE (16 imports)
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class FileProcessor {
// ...
}
// AFTER (1 import)
import module java.base;
public class FileProcessor {
// Everything is in java.base -- one import covers it all
}
The pattern is clear: for typical application code, import module java.base; eliminates 80-90% of your import statements. Add one or two more module imports for database, HTTP, or desktop work, and you cover virtually everything.
Now we get to the second half of Java’s simplification story. If module imports tackle the import problem, JEP 477 tackles the ceremony problem — the amount of boilerplate code required to write even the simplest Java program.
Here is the classic “Hello, World!” in Java, compared to other languages:
// Java (traditional) -- 5 lines of ceremony
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
// Python -- 1 line
// print("Hello, World!")
// JavaScript -- 1 line
// console.log("Hello, World!");
// Go -- still needs ceremony, but less
// func main() {
// fmt.Println("Hello, World!")
// }
To write one line of useful code in Java, you need to understand:
That is a lot of concepts to explain before a new programmer can see their first output. JEP 477 removes most of this ceremony.
The first simplification: you no longer need public static void main(String[] args). Here are the new options:
// Option 1: Simplest possible main method
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
// Option 2: Main with args (if you need command-line arguments)
class HelloWorld {
void main(String[] args) {
System.out.println("Hello, " + args[0] + "!");
}
}
// Option 3: Static main still works (backward compatible)
class HelloWorld {
static void main() {
System.out.println("Hello, World!");
}
}
Notice what changed:
public required — the main method does not need to be public anymorestatic required — it can be an instance method, meaning the JVM will create an instance of the class and call main() on itString[] args required — if you do not need command-line arguments, leave them outThis is a significant improvement. A beginner can now write a program that uses only concepts they understand: a class has a method, the method does something. No static, no access modifiers, no array parameters.
The second simplification goes further: you do not need a class declaration at all. If a Java source file contains methods (including main) but no class declaration, the compiler wraps them in an implicitly declared class.
// File: HelloWorld.java
// No class declaration needed!
void main() {
System.out.println("Hello, World!");
}
That is it. One method, one line of logic. The file compiles and runs like any other Java program:
// Compile and run // $ javac HelloWorld.java // $ java HelloWorld // Output: Hello, World! // Or use the source-file launcher (no explicit compilation needed) // $ java HelloWorld.java // Output: Hello, World!
Behind the scenes, the compiler generates a class with the same name as the file (minus the .java extension). But you never see it, and you never have to think about it.
You can add fields and helper methods to an implicitly declared class, just like a regular class:
// File: Greeting.java
// Fields and helper methods work in implicitly declared classes
String greeting = "Hello";
void main() {
String name = "Java 25";
System.out.println(greet(name));
}
String greet(String name) {
return greeting + ", " + name + "!";
}
// Output: Hello, Java 25!
With multiple valid main method signatures now possible, the JVM follows a specific priority order when looking for the entry point. Understanding this protocol is important because it determines which main method runs if you have multiple candidates.
The JVM tries these signatures in order, picking the first one it finds:
| Priority | Signature | Notes |
|---|---|---|
| 1 | static void main(String[] args) |
Traditional — highest priority for backward compatibility |
| 2 | static void main() |
Static without args — new in Java 25 |
| 3 | void main(String[] args) |
Instance method with args — JVM creates instance first |
| 4 | void main() |
Instance method without args — simplest form |
Key points:
String[] args take priority over methods without argspublic, protected, package-private, and private are all valid (though private only works in implicitly declared classes)public static void main(String[] args), it still runs first// Example: Which main runs?
class MultipleMain {
// Priority 1 -- this one wins
static void main(String[] args) {
System.out.println("Static main with args");
}
// Priority 4 -- never reached
void main() {
System.out.println("Instance main without args");
}
}
// Output: Static main with args
For instance main methods, the JVM creates an instance of the class using the no-argument constructor before calling main(). This means the class must have a no-arg constructor (which every class has by default unless you declare a different constructor).
The real power comes when you combine both features: module imports and implicitly declared classes. Together, they give you what the JDK team calls “simple source files” — Java programs that are as concise as scripts.
Here is the transformation, step by step:
// Step 1: Traditional Java (Java 8 style)
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;
public class NameProcessor {
public static void main(String[] args) {
List names = new ArrayList<>(List.of("alice", "bob", "charlie"));
List result = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(result);
}
}
// Step 2: Add module imports (remove import block)
import module java.base;
public class NameProcessor {
public static void main(String[] args) {
List names = new ArrayList<>(List.of("alice", "bob", "charlie"));
List result = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(result);
}
}
// Step 3: Use instance main (remove public, static, String[] args)
import module java.base;
public class NameProcessor {
void main() {
List names = new ArrayList<>(List.of("alice", "bob", "charlie"));
List result = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(result);
}
}
// Step 4: Implicitly declared class (remove class declaration)
import module java.base;
void main() {
List names = new ArrayList<>(List.of("alice", "bob", "charlie"));
List result = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(result);
}
From 14 lines down to 8. From 3 import statements and a class declaration down to a single module import. The actual logic did not change at all — it is the same streams, the same collections, the same method references. But the noise is gone.
There is one more convenience: in implicitly declared classes (files without a class declaration), java.base is automatically imported. You do not even need import module java.base;. This means the simplest version of the program above is:
// File: NameProcessor.java
// No imports needed! java.base is automatically imported
// for implicitly declared classes.
void main() {
var names = new ArrayList<>(List.of("alice", "bob", "charlie"));
var result = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(result);
}
This is as simple as Java gets. No imports, no class, no public static void main. Just the logic. Run it with java NameProcessor.java and it works.
Here are practical examples showing simple source files for common tasks:
// Example 1: Read a file and count words
// File: WordCounter.java
void main() {
var path = Path.of("document.txt");
try {
var lines = Files.readAllLines(path);
long wordCount = lines.stream()
.flatMap(line -> Stream.of(line.split("\\s+")))
.filter(word -> !word.isEmpty())
.count();
System.out.println("Word count: " + wordCount);
} catch (IOException e) {
System.err.println("Error reading file: " + e.getMessage());
}
}
// Example 2: Simple HTTP request
// File: QuickFetch.java
import module java.net.http;
void main() throws Exception {
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.github.com/zen"))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.body());
}
// Example 3: CSV processor with helper methods
// File: CsvProcessor.java
void main() {
var data = List.of(
"Alice,Engineering,95000",
"Bob,Marketing,78000",
"Charlie,Engineering,102000",
"Diana,Marketing,81000"
);
var avgByDept = data.stream()
.map(line -> line.split(","))
.collect(Collectors.groupingBy(
parts -> parts[1],
Collectors.averagingDouble(parts -> Double.parseDouble(parts[2]))
));
avgByDept.forEach((dept, avg) ->
System.out.printf("%s: $%,.0f%n", dept, avg));
}
// Output:
// Engineering: $98,500
// Marketing: $79,500
These features are not intended to replace traditional Java class declarations in production codebases. They serve specific purposes where ceremony reduction matters most:
This is the primary motivation. When teaching Java to beginners, you can now start with:
// Lesson 1: Your first Java program
void main() {
System.out.println("Hello, World!");
}
// Lesson 2: Variables and types
void main() {
String name = "Student";
int age = 20;
double gpa = 3.8;
System.out.println(name + " is " + age + " years old with GPA " + gpa);
}
// Lesson 3: Loops
void main() {
for (int i = 1; i <= 10; i++) {
System.out.println(i + " x 7 = " + (i * 7));
}
}
// Introduce classes, static, and access modifiers LATER,
// when students are ready for object-oriented concepts
Educators can now teach procedural programming first, then introduce object-oriented concepts when students have a foundation in variables, loops, and methods. This matches how most programming courses are structured -- Java was the outlier that forced OOP from line one.
Java has never been a scripting language, but simple source files bring it closer. Quick tasks like file processing, data transformation, or API testing become practical without setting up a full project:
// File: CleanupLogs.java
// Run with: java CleanupLogs.java
void main() throws Exception {
var logDir = Path.of("/var/log/myapp");
var cutoff = Instant.now().minus(Duration.ofDays(30));
try (var files = Files.list(logDir)) {
var deleted = files
.filter(f -> f.toString().endsWith(".log"))
.filter(f -> {
try {
return Files.getLastModifiedTime(f).toInstant().isBefore(cutoff);
} catch (IOException e) {
return false;
}
})
.peek(f -> {
try { Files.delete(f); } catch (IOException e) {
System.err.println("Failed to delete: " + f);
}
})
.count();
System.out.println("Deleted " + deleted + " old log files");
}
}
When you want to quickly test a library feature, validate an algorithm, or experiment with an API, simple source files remove the friction of creating a class, picking a package, and writing the main method signature:
// File: TestRegex.java
// Quick experiment -- does my regex work?
void main() {
var pattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
var input = "The release date is 2025-09-16 and EOL is 2033-09-16.";
var matcher = pattern.matcher(input);
while (matcher.find()) {
System.out.printf("Full match: %s | Year: %s | Month: %s | Day: %s%n",
matcher.group(0), matcher.group(1), matcher.group(2), matcher.group(3));
}
}
// Output:
// Full match: 2025-09-16 | Year: 2025 | Month: 09 | Day: 16
// Full match: 2033-09-16 | Year: 2033 | Month: 09 | Day: 16
Competitive programmers care about typing speed and code brevity. Removing the class declaration, access modifiers, and static keyword saves time and reduces mistakes under pressure. The auto-import of java.base means all standard library types are available without import statements.
Implicitly declared classes are not full replacements for regular classes. They have specific limitations you need to understand:
| Restriction | Why | What to Do Instead |
|---|---|---|
| Cannot be referenced by other classes | Implicitly declared classes have no accessible name | Use a regular class declaration when other classes need to reference it |
| No constructors | The JVM uses the default no-arg constructor | Use initialization in field declarations or the main method |
No extends or implements |
No class declaration means no inheritance clause | Use a regular class if you need to extend or implement |
Cannot define static members (except main) |
Instance-oriented by design; static methods in an unnamed class add complexity | Use instance methods, or switch to a regular class |
| No package declaration | Implicitly declared classes are always in the unnamed package | Use a regular class for packaged code |
| Not part of a module | Lives in the unnamed module | Use a regular class for modular applications |
| One per file | Same rule as top-level classes | Keep each implicitly declared class in its own file |
Despite the restrictions, you can still do a lot:
// File: FeatureDemo.java
// All of these work in an implicitly declared class:
// Instance fields
int counter = 0;
String prefix = "Item";
// Instance methods
String format(int num) {
return prefix + "-" + String.format("%04d", num);
}
void processItems(List items) {
for (String item : items) {
counter++;
System.out.println(format(counter) + ": " + item);
}
}
// Main entry point
void main() {
var items = List.of("Keyboard", "Monitor", "Mouse", "Headset");
processItems(items);
System.out.println("Total items processed: " + counter);
}
// Output:
// Item-0001: Keyboard
// Item-0002: Monitor
// Item-0003: Mouse
// Item-0004: Headset
// Total items processed: 4
Now that you understand both features, here are guidelines for when to use them:
| Scenario | Use Simplified? | Why |
|---|---|---|
| Teaching beginners | Yes | Reduces cognitive load, lets students focus on fundamentals |
| Quick scripts and one-off tools | Yes | Faster to write, no project setup needed |
| Prototyping and experiments | Yes | Get to the interesting code faster |
| Competitive programming | Yes | Saves keystrokes and reduces boilerplate errors |
| Production application code | No | Use full class declarations for maintainability |
| Library code | No | Other classes need to reference your types |
| Code that needs inheritance | No | Implicitly declared classes cannot extend or implement |
| Multi-class applications | No | Classes need to reference each other by name |
Module imports are useful even in production code. There is a reasonable debate about whether to use them:
Arguments for using import module java.base; in production:
Arguments against:
Recommendation: For new projects and teams open to change, module imports are a net positive -- use them. For existing projects with established conventions, discuss with your team before switching. The important thing is consistency within a codebase.
If you want to adopt these features, here is a sensible path:
Let us build a complete, practical program using both features. This script reads a CSV file of employees, groups them by department, calculates statistics, and outputs a formatted report.
// File: EmployeeReport.java
// Run with: java EmployeeReport.java employees.csv
// No class declaration, no explicit imports -- java.base is auto-imported
void main(String[] args) {
if (args.length == 0) {
System.err.println("Usage: java EmployeeReport.java ");
System.exit(1);
}
var file = Path.of(args[0]);
try {
var lines = Files.readAllLines(file);
// Skip header, parse each line
var employees = lines.stream()
.skip(1)
.map(line -> line.split(","))
.filter(parts -> parts.length >= 3)
.toList();
// Group by department
var byDept = employees.stream()
.collect(Collectors.groupingBy(parts -> parts[1].trim()));
// Print report
System.out.println("=" .repeat(50));
System.out.println("EMPLOYEE REPORT - " + LocalDate.now());
System.out.println("=".repeat(50));
byDept.forEach((dept, members) -> {
var avgSalary = members.stream()
.mapToDouble(p -> Double.parseDouble(p[2].trim()))
.average()
.orElse(0);
var maxSalary = members.stream()
.mapToDouble(p -> Double.parseDouble(p[2].trim()))
.max()
.orElse(0);
System.out.printf("%n Department: %s%n", dept);
System.out.printf(" Headcount: %d%n", members.size());
System.out.printf(" Avg Salary: $%,.0f%n", avgSalary);
System.out.printf(" Max Salary: $%,.0f%n", maxSalary);
System.out.println(" Members:");
members.forEach(m ->
System.out.printf(" - %s ($%s)%n", m[0].trim(), m[2].trim()));
});
System.out.println("\n" + "=".repeat(50));
System.out.printf("Total employees: %d%n", employees.size());
System.out.println("=".repeat(50));
} catch (IOException e) {
System.err.println("Error reading file: " + e.getMessage());
System.exit(1);
}
}
That is a complete, runnable Java program with file I/O, streams, collectors, date formatting, and string formatting -- all in a single method with no class declaration and no imports. Run it with java EmployeeReport.java employees.csv and it just works.
Compare the first line of this program (void main(String[] args)) to the traditional version (public class EmployeeReport { public static void main(String[] args) {). The reduction in ceremony is dramatic, and the code is easier to read because it focuses entirely on what the program does rather than how Java requires it to be structured.
Java 25 finalizes two features that make the language significantly more approachable for beginners and more convenient for experienced developers writing scripts, prototypes, and small programs:
| Feature | What It Does | Best For |
|---|---|---|
| Module Import Declarations (JEP 476) | Import all public types from a module with import module java.base; |
All Java code -- reduces import boilerplate everywhere |
| Instance Main Methods (part of JEP 477) | Write void main() without public static String[] args |
Simpler entry points, teaching, scripts |
| Implicitly Declared Classes (part of JEP 477) | Write methods without a class declaration; file is the class | Scripts, prototypes, teaching, quick experiments |
| Auto-import of java.base | Implicitly declared classes get java.base imported automatically |
Zero-import Java programs for simple tasks |
These features do not change how production Java applications are built. They add a new, simpler way to write Java when the full ceremony is unnecessary. Think of them as Java's casual Friday -- you can still wear the suit when you need to, but now you have the option to dress down when the situation calls for it.
The combination of module imports, instance main methods, and implicitly declared classes means that Java 25 has the simplest on-ramp of any Java version in the language's 30-year history. A new programmer can write their first Java program in one line. That is a significant achievement for a language that has always prioritized explicitness and structure.