java module imports feature imagejava module imports feature image
HappyCoders Glasses

Importing Modules in Java: Module Import Declarations

Sven Woltmann
Sven Woltmann
Last update: December 4, 2024

In this article, you will learn:

  • How do you import entire Java modules using “import module”?
  • How to resolve ambiguities in module imports.
  • What are transitive module imports, and how do they work?
  • Which modules are imported by default?

Let’s start with a very brief overview...

Java Imports

Since Java 1.0, we have been able to import individual classes (“single-type-import declaration”) or entire packages (“type-import-on-demand declaration”) using the import statement, e.g.:

import java.util.*;
import java.util.stream.Stream;Code language: Java (java)

The classes of the java.lang package have always been imported automatically. Therefore, we do not need to specify import statements for classes like Object, String, Integer, Exception, etc.

Module Import Declarations

With import module, we can now import entire modules since Java 23¹. This allows us to use all classes that are located within a module and exported by it directly.

In the following example, we import the module java.base and can, therefore, use the classes List, Map, Stream, and Collectors without having to import them individually or by package (String and Character are in the package java.lang and have always been imported automatically).

import module java.base;

public class ModuleImportTest {

  public static Map<Character, List<String>> groupByFirstLetter(String... values) {
    return Stream.of(values).collect(
        Collectors.groupingBy(s -> Character.toUpperCase(s.charAt(0))));
  }
}Code language: Java (java)

If you save this code example in the file ModuleImportTest.java, you can compile the class in Java 23 as follows (in Java 24, you must replace the parameter --source 23 with --source 24):

javac --enable-preview --source 23 ModuleImport.javaCode language: plaintext (plaintext)

To use import module, it is not necessary for the importing class itself to be in a module, as seen in the previous example.

Simple Source Files (also known as “Implicitly declared classes”) and JShell automatically import the java.base module, i.e., you can use classes like List and Map there without explicit imports¹.

¹ Currently, these statements only apply when you activate preview features with --enable-preview --source <Java version>.

Ambiguous Class Names

Sometimes, a class name is not unique. In the following example, there is a List class in both the imported module java.base (java.util.List) and the module java.desktop (java.awt.List):

import module java.base;     // ← Contains java.util.List
import module java.desktop;  // ← Contains java.awt.List

public class Ambiguous {
  List list;                 // ← Ambiguous reference to List
}Code language: Java (java)

If you save the file as Ambiguous.java and then compile it with Java 23 as follows:

javac --enable-preview --source 23 Ambiguous.javaCode language: plaintext (plaintext)

... or with Java 24 as follows:

javac --enable-preview --source 24 Ambiguous.javaCode language: plaintext (plaintext)

... then the compiler will abort with the following error message:

Ambiguous.java:5: error: reference to List is ambiguous
  List list;
  ^
  both class java.awt.List in java.awt and interface java.util.List in java.util matchCode language: plaintext (plaintext)

This means that the compiler does not know which of the two List classes you mean.

Resolving Ambiguous Class Names

Let's assume you want to use java.util.List (and not java.awt.List). Then you have two options to resolve this ambiguity:

Option 1: You also import the class java.util.List directly:

import module java.base;
import module java.desktop;

import java.util.List;  // ← Ambiguity resolved by single-type-import declaration

public class Ambiguous {
  List list;
}Code language: Java (java)

Option 2: You also import the package java.util (in Java terminology, this is not called “package import” but “type import on demand declaration”):

import module java.base;
import module java.desktop;

import java.util.*;  // ← Ambiguity resolved by type-import-on-demand declaration

public class Ambiguous {
  List list;
}Code language: Java (java)

The second option has only been available since Java 24. Compiling the last example with Java 23 also results in the error message “reference to List is ambiguous.”

By the way, ambiguous class names can also occur – although rarely – if you only import one module. For example, the module java.desktop contains both the interface javax.swing.text.Element and the class javax.swing.text.html.parser.Element.

Transitive Module Imports

When a module imported with import module transitively imports a third module, you can also use all classes of the exported packages of this third module without explicit imports.

I want to explain this using an example with the modules java.sql and java.xml.

  • The module java.sql has a transitive dependency on the module java.xml.
  • The module java.sql exports the packages java.sql and javax.sql.
  • The module java.xml exports the packages javax.xml and org.w3c.dom, each with numerous sub-packages.

The following graphic shows the modules, their dependencies, and the exported packages:

java module import declarations

The module declaration of the java.sql module looks like this:

module java.sql {
  . . .
  requires transitive java.xml;

  exports java.sql;
  exports javax.sql;
  . . .
}Code language: Java (java)

And the module declaration of java.xml:

module java.xml {
  exports javax.xml;
  exports javax.xml.parsers;
  . . .
}Code language: Java (java)

If we now write a program that imports the module java.sql, then we do not need explicit imports for the classes SAXParserFactory and SAXParser from the package javax.xml.parsers of the java.xml module – and also no explicit import of this module:

import module java.sql;  // ← Transitively imports module java.xml
                         //   and its exported packages, e.g. javax.xml.parsers
. . .
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
. . .Code language: Java (java)

That's because from the transitive dependency of module java.sql on module java.xml and the fact that java.xml exports the package javax.xml.parsers, it follows that the program can also access all classes of the package javax.xml.parsers without explicit imports.

Note that in Java 23, importing the module java.se (an aggregator module with dependencies on all modules of the Java Standard Edition “Java SE”) does not make the classes of the java.base module available. This will change in Java 24.

Automatic java.base Import in JShell

Once Module Import Declarations become production-ready, JShell will automatically import the java.base module. Currently, you can already enable this with --enable-preview:

$ jshell --enable-preview
|  Welcome to JShell -- Version 23.0.1
|  For an introduction type: /help intro

jshell> /imports
|    import java.baseCode language: plaintext (plaintext)

If you currently start JShell without --enable-preview and enter the /imports command, you will see the following ten standard package imports instead:

$ jshell --enable-preview
|  Welcome to JShell -- Version 23.0.1
|  For an introduction type: /help intro

jshell> /imports
|    import java.io.*
|    import java.math.*
|    import java.net.*
|    import java.nio.file.*
|    import java.util.*
|    import java.util.concurrent.*
|    import java.util.function.*
|    import java.util.prefs.*
|    import java.util.regex.*
|    import java.util.stream.*Code language: plaintext (plaintext)

Conclusion

Module Import Declarations can make Java programs shorter and easier to maintain by allowing you to import entire modules rather than individual classes and packages. On the other hand, they can also further fuel the eternal discussion about whether to import classes individually or by package.

In Simple Source Files (or Implicitly Declared Classes) and JShell, the java.base module is automatically imported so that all classes of this module can be used directly without imports.

Module Import Declarations are still in the preview phase until at least Java 24 and must be activated with --enable-preview --source <Java version>.

Would you like to always stay up-to-date and be informed as soon as a new article is published on HappyCoders.eu? Then click here to sign up for the HappyCoders.eu newsletter.