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.java
Code 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¹.
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.java
Code language: plaintext (plaintext)
... or with Java 24 as follows:
javac --enable-preview --source 24 Ambiguous.java
Code 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 match
Code 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.”
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 modulejava.xml
. - The module
java.sql
exports the packagesjava.sql
andjavax.sql
. - The module
java.xml
exports the packagesjavax.xml
andorg.w3c.dom
, each with numerous sub-packages.
The following graphic shows the modules, their dependencies, and the exported packages:
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.
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.base
Code 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.