Module Import Declarations (JEP 511): One Import to Rule Them All
The Problem: Import Hell
Every Java developer has experienced this. You open a file and before you see a single line of business logic, you wade through a wall of imports:
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
This is not useful documentation — it is noise. The IDE manages it, developers scroll past it, and it accumulates cruft over time. Wildcard imports (import java.util.*) help but they only cover one package at a time.
The Java 25 Solution: import module
JEP 511 introduces a new import form:
import module java.base;
This single line imports all public top-level types from all packages exported by the java.base module. That includes java.util, java.io, java.nio, java.time, java.lang, java.math, java.net, and more — all at once.
Before vs. After
Before Java 25
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
public class UserReport {
public static Map<String, List<String>> groupUsersByCity(Path csvFile) throws IOException {
return Files.lines(csvFile)
.map(line -> line.split(","))
.collect(Collectors.groupingBy(
parts -> parts[1].trim(),
Collectors.mapping(parts -> parts[0].trim(), Collectors.toList())
));
}
}
After Java 25
import module java.base;
public class UserReport {
public static Map<String, List<String>> groupUsersByCity(Path csvFile) throws IOException {
return Files.lines(csvFile)
.map(line -> line.split(","))
.collect(Collectors.groupingBy(
parts -> parts[1].trim(),
Collectors.mapping(parts -> parts[0].trim(), Collectors.toList())
));
}
}
The logic is identical. The import wall is gone.
What Exactly Gets Imported?
A module import declaration import module M is equivalent to a type-import-on-demand (import pkg.*) for:
- Every package directly exported by module
M - Every package transitively exported by modules that
Mrequires withtransitive
Example: import module java.sql
The java.sql module requires java.logging and java.xml with transitive. So import module java.sql gives you:
java.sql.*— JDBC typesjava.util.logging.*— via transitive dependency onjava.loggingorg.xml.sax.*,javax.xml.*— via transitive dependency onjava.xml- Everything from
java.baseis implicitly available without an import (it’s always on the module path)
Common Module Imports
Here are the most useful module imports and what they cover:
import module java.base;
// Covers: java.lang, java.util, java.io, java.nio, java.time,
// java.math, java.net, java.security, java.text, java.util.concurrent, ...
import module java.sql;
// Covers: java.sql, javax.sql, + transitive exports
import module java.net.http;
// Covers: java.net.http (HttpClient, HttpRequest, HttpResponse)
import module java.xml;
// Covers: org.xml.sax, javax.xml, org.w3c.dom, ...
import module java.logging;
// Covers: java.util.logging
import module java.desktop;
// Covers: java.awt, javax.swing, java.applet, java.beans, ...
You can combine them:
import module java.base;
import module java.net.http;
import module java.sql;
Resolving Ambiguity
What happens when two modules export a type with the same simple name?
import module java.base; // exports java.util.Date
import module java.sql; // exports java.sql.Date
Both modules export a Date. If you write Date in your code, the compiler will report an ambiguity error — just like with wildcard package imports.
Resolution: use a specific import for the one you actually want.
import module java.base;
import module java.sql;
import java.sql.Date; // explicit import wins over module imports
Date today = new Date(System.currentTimeMillis()); // java.sql.Date
This follows the same priority rules that have always existed in Java:
- Explicit single-type import wins
- Module import / wildcard import loses to explicit
Module Imports in Non-Modular Code
You do not need to be writing modular Java (with a module-info.java) to use module imports. They work in regular class files too:
// Regular Java file — no module-info.java required
import module java.base;
public class DataProcessor {
public List<String> process(Stream<String> input) {
return input
.filter(s -> !s.isBlank())
.map(String::trim)
.sorted()
.collect(Collectors.toList());
}
}
Real-World Example: HTTP Client
Here is a practical HTTP utility using java.net.http with module imports:
import module java.base;
import module java.net.http;
public class GitHubClient {
private static final HttpClient HTTP = HttpClient.newHttpClient();
private static final String BASE = "https://api.github.com";
public static String fetchUser(String username) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE + "/users/" + username))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = HTTP.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IOException("GitHub API returned " + response.statusCode());
}
return response.body();
}
public static void main(String[] args) throws Exception {
System.out.println(fetchUser("openjdk"));
}
}
No imports for URI, IOException, HttpClient, HttpRequest, HttpResponse — they all come from the two module imports at the top.
When NOT to Use Module Imports
Module imports are great for exploratory code, scripts, demos, and utility classes that touch many APIs. But in a large codebase, explicit imports have advantages:
- Readability: explicit imports tell future readers exactly what external types this class depends on
- Refactoring safety: IDEs track explicit imports; renaming or moving a class updates them automatically
- Conflict prevention: in a large monorepo with many third-party modules, wildcards invite
Date/List-style ambiguities
A sensible guideline: use module imports in compact source files, scripts, tests, and demos. Use explicit imports in production classes with complex dependency graphs.
Summary
| Before Java 25 | After Java 25 | |
|---|---|---|
Import everything from java.base | 20+ individual imports OR multiple .* lines | import module java.base; |
| Import JDBC + XML | import java.sql.*; import org.xml.sax.*; ... | import module java.sql; |
| Ambiguity resolution | Same as wildcard imports | Same: explicit import wins |
| Requires modular code | N/A | No — works in any file |