Module Import Declarations

Import every package a module exports with one import module line, know what it pulls in, and settle the ambiguities it can cause.

Syntaximport module java.sql; // every package java.sql exports, plus its transitive requirements import module java.net.http; import java.sql.Date; // a single-type import settles a clash

import module java.sql; imports, on demand, every public top-level type in every package that the module java.sql exports. One line replaces a list of java.sql and javax.sql imports. The feature was a preview in Java 23 and 24 and is final in Java 25 (JEP 511), so it needs no flags.

What a module import covers

It covers the packages the module exports, plus the packages exported by modules it requires transitive. java.sql requires java.logging, java.xml and java.transaction.xa transitively, so import module java.sql; also makes java.util.logging.Level and javax.xml.parsers.DocumentBuilderFactory available by simple name. import module java.base; covers java.util, java.io, java.nio.file, java.time, java.util.concurrent, java.util.stream, java.net, java.math and the other packages of the base module. Sub-packages are separate packages, but a module usually exports them too, so they are included.

Your code does not have to be in a module to use it. An ordinary class on the class path can start with import module java.base;. Inside a named module the imported module must be one your module reads, so the requires line in module-info.java is still needed. The import is only about names; it adds nothing to the module graph. A library JAR on the class path has no module name, so it cannot be imported this way.

Compact source files get java.base for free

A compact source file (a bare void main() with no class declaration) behaves as if it starts with import module java.base;. That is why such a file can use List, Map, Path and Files without a single import line.

Ambiguity, and how to settle it

Module imports bring in many names at once, so clashes are more likely. java.base has java.util.Date and java.sql has java.sql.Date; with both modules imported, using Date fails with "reference to Date is ambiguous". The fix is a more specific import. A single-type import (import java.sql.Date;) beats every on-demand import, and a package wildcard (import java.sql.*;) beats a module import.

Where it belongs

Module imports suit scripts, small command-line tools, prototypes, teaching material and quick experiments against an API such as java.net.http. In a long-lived service most teams keep explicit single-type imports: the IDE writes them, reviewers see exactly which Date a file means, and a JDK upgrade that adds a new public type to an imported module cannot create an ambiguity in a file nobody touched.

Example

Example · java
import module java.base;

public class Main {
    public static void main(String[] args) {
        // List, Map, TreeMap and Collectors all come from the one module import
        List<String> lines = List.of(
                "GET /orders 200",
                "POST /orders 201",
                "GET /orders 200",
                "GET /customers 404");

        Map<String, Long> byStatus = lines.stream()
                .map(line -> line.substring(line.lastIndexOf(' ') + 1))
                .collect(Collectors.groupingBy(s -> s, TreeMap::new, Collectors.counting()));

        System.out.println(byStatus);
    }
}

// Output:
// {200=2, 201=1, 404=1}

When to use it

  • Write a one-file smoke test for a health endpoint using import module java.net.http.
  • Prototype a JDBC query against the java.sql API without listing every interface import.
  • Show a newcomer working code in a compact source file before explaining individual imports.

More examples

Transitive modules come along

Level and DocumentBuilderFactory are not in java.sql at all; they arrive because java.sql declares requires transitive java.logging and java.xml. That is the rule to remember when a module import makes more names visible than you expected.

Example · java
import module java.sql;

public class Main {
    public static void main(String[] args) {
        // java.sql exports java.sql and javax.sql
        System.out.println(Connection.class.getName());
        System.out.println(DataSource.class.getName());
        // java.sql requires java.logging and java.xml transitively
        System.out.println(Level.WARNING.getClass().getName());
        System.out.println(DocumentBuilderFactory.class.getName());
    }
}

// Output:
// java.sql.Connection
// javax.sql.DataSource
// java.util.logging.Level
// javax.xml.parsers.DocumentBuilderFactory

Fixing the Date ambiguity in a compact source file

The compact source file already imports java.base, so adding java.sql brings two Date classes into view. The single-type import picks one and the rest of both modules stays available.

Example · java
import module java.sql;
import java.sql.Date; // without this line, Date is ambiguous with java.util.Date

void main() {
    Date day = Date.valueOf("2026-01-15");
    List<String> tables = List.of("orders", "customers"); // from the implicit java.base import
    IO.println(day.getClass().getName() + " " + day);
    IO.println(tables);
}

// Output:
// java.sql.Date 2026-01-15
// [orders, customers]

A small HTTP check with two module imports

Two lines replace the half-dozen imports that HttpServer, HttpClient, HttpRequest and HttpResponse would otherwise need, which is exactly the kind of throwaway tool module imports are good for. InetSocketAddress, URI and StandardCharsets come from the implicit java.base import.

Example · java
import module java.net.http;
import module jdk.httpserver;

void main() throws Exception {
    HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
    server.createContext("/health", exchange -> {
        byte[] body = "{\"status\":\"UP\"}".getBytes(StandardCharsets.UTF_8);
        exchange.getResponseHeaders().set("Content-Type", "application/json");
        exchange.sendResponseHeaders(200, body.length);
        try (OutputStream out = exchange.getResponseBody()) {
            out.write(body);
        }
    });
    server.start();
    try (HttpClient client = HttpClient.newHttpClient()) {
        URI uri = URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/health");
        HttpResponse<String> res = client.send(HttpRequest.newBuilder(uri).build(),
                HttpResponse.BodyHandlers.ofString());
        IO.println(res.statusCode() + " " + res.body());
    } finally {
        server.stop(0);
    }
}

// Output:
// 200 {"status":"UP"}

Discussion

  • Be the first to comment on this lesson.