Java 25 Module Import Declarations with Examples

Introduction A typical Java file starts with a long list of imports - java.util.List, java.util.Map, java.util.stream.Collectors, java.nio.file.Files, java.time.LocalDate - often managed by the IDE and rarely read. On-demand imports like import java.util.*; help, but only for one package at a time. Since Java 9, related packages are grouped into modulesClick to read: modules, so it is natural to import a module as a whole. After previews in Java 23 and 24, Java 25Click to read tutorial: Overview of New Features in Java 25 (JEP 511) finalized module import declarations: import module java.base; imports every public type of every package that the module exports. This tutorial explains them with examples - how they work, resolving ambiguous names, and when to use them. Importing a whole module
One module import instead of many package imports
//ModuleImportDemo.java
import module java.base;        // java.util, java.util.stream, java.time, java.nio.file and 50+ more packages

public class ModuleImportDemo {
  public static void main(String[] args) throws IOException {
    List<String> names = List.of("Tom", "Harry", "Sam");
    Map<Integer, List<String>> byLength = names.stream().collect(Collectors.groupingBy(String::length));
    System.out.println("Grouped by length: " + byLength);

    LocalDate release = LocalDate.of(2025, Month.SEPTEMBER, 16);
    System.out.println("Java 25 was released on a " + release.getDayOfWeek());

    Path file = Files.createTempFile("demo", ".txt");
    Files.writeString(file, String.join(",", names));
    System.out.println("File contains: " + Files.readString(file));
    Files.delete(file);
  }
}
 OUTPUT of the above code
Grouped by length: {3=[Tom, Sam], 5=[Harry]}
Java 25 was released on a TUESDAY
File contains: Tom,Harry,Sam
Explanation of the code
  • import module java.base; made List and Map (java.util), Collectors (java.util.stream), LocalDate and Month (java.time), Path and Files (java.nio.file) and IOException (java.io) all available with one line.
  • A module import is equivalent to an on-demand import (import p.*;) of every package the module exports - including, through requires transitive, the packages of modules it depends on transitively. Importing java.sql, for example, also imports java.xml's packages.
  • The importing code does not itself need to be in a module - a class on the class path, as here, can import modules too.
Ambiguous names Importing whole modules makes it more likely that two packages contain a type with the same simple name. The classic example is List, which exists both as java.util.List and java.awt.List in the java.desktop module -
Two module imports with the same type name
//Ambiguous.java
import module java.base;
import module java.desktop;

public class Ambiguous {
  List<String> names = new ArrayList<>();
}
 OUTPUT on compiling the above code
Ambiguous.java:6: error: reference to List is ambiguous
  List<String> names = new ArrayList<>();
  ^
  both class java.awt.List in java.awt and interface java.util.List in java.util match
1 error
The solution is a single-type import, which always takes precedence over module and on-demand imports -
Resolving the ambiguity with a single-type import
//Resolved.java
import module java.base;
import module java.desktop;
import java.util.List;           // shadows java.awt.List

public class Resolved {
  public static void main(String[] args) {
    List<String> names = new ArrayList<>(List.of("Tom", "Harry"));
    Dimension size = new Dimension(800, 600);          // from java.desktop's java.awt
    System.out.println(names + " in a window of " + size.width + "x" + size.height);
  }
}
 OUTPUT of the above code
[Tom, Harry] in a window of 800x600
The rule of precedence is: single-type imports (import java.util.List;) shadow on-demand imports (import java.util.*;), which shadow module imports. So a module import can never silently change the meaning of a name that is imported more specifically.
Module imports and compact source files Compact source filesClick to read tutorial: Java 25 Compact Source Files and Instance main Methods import java.base automatically, as if they began with import module java.base;. This is what lets a beginner use List, Map or LocalDate in a small program without learning about imports first. Your own modules can be imported the same way, by name, if they are on the module path: import module com.javabrahman.greetings;. When to use module imports
  • Good fits: scripts, prototypes, tests, teaching examples and compact source files, where convenience matters more than knowing exactly where each type comes from - and code which uses an API spread over many packages of one module.
  • Single-type imports remain clearer in large production codebases, where the import list documents a file's dependencies, and IDEs manage it anyway.
  • Whichever style you choose, keep it consistent within a project.
Summary In this tutorial we saw how module import declarations, final in Java 25, import every package a module exports with a single import module line. We replaced a list of package imports with import module java.base;, saw the compile error when two imported modules contain a List, and resolved it with a single-type import, which always takes precedence. Lastly, we looked at module imports in compact source files, and when to use them. Next, read about flexible constructor bodiesClick to read tutorial: Java 25 Flexible Constructor Bodies with Examples.