4 Module Import Declarations

You can import all packages exported by a module with one declaration.

Note:

This is a preview feature. A preview feature is a feature whose design, specification, and implementation are complete, but is not permanent. A preview feature may exist in a different form or not at all in future Java SE releases. To compile and run code that contains preview features, you must specify additional command-line options. See Preview Language and VM Features. For more information about module import declarations, see JEP 494

Consider the following example, which imports four classes:

import java.util.Map;    
import java.util.function.Function;  
import java.util.stream.Collectors;  
import java.util.stream.Stream;    

public class FruitMap {
    public static void main(String[] args) {
        String[] fruits = new String[] { "apple", "berry", "citrus" };
        Map<String, String> m = Stream
            .of(fruits)
            .collect(Collectors.toMap(
                s -> s.toUpperCase().substring(0,1),
                Function.identity()));
        m.forEach((k, v) ->
            System.out.println(k + " " + v));
    }
}

You can replace the four single-type-import declarations in this example with type-import-on-demand declarations. However, you still need three of them:

import java.util.*;    
import java.util.function.*;  
import java.util.stream.*;

Because the module java.base exports the packages java.util, java.util.function and java.util.stream, you can replace these three declarations with one module import declaration:

import module java.base;

A module import declaration has the following form:

import module M;

It imports, on demand, all of the public top-level class and interfaces in the following:

  • The packages exported by the module M to the current module.

  • The packages exported by the modules that are read by the current module due to reading the module M. This enables a program to use the API of a module, which might refer to classes and interfaces from other modules, without having to import all those other modules.

    For example, the module import declaration import module java.sql has the same effect as import java.sql.* plus on-demand imports for the indirect exports of the java.sql module, which include the packages java.logging and java.xml.

Ambiguous Imports

It's possible to import classes with the same simple name from different packages with module import declarations. However, this can lead to compile-time errors. The following example uses both java.awt.List and java.util.List:

import java.awt.Frame;
import java.awt.Label;
import java.awt.List;
import java.awt.event.WindowAdapter;   
import java.awt.event.WindowEvent;    
import java.util.Arrays;

public class FruitApp {    
      
    FruitApp(java.util.List<String> fruits) {  
        Frame f = new Frame();  
        Label l = new Label("Fruits");   
        List lst = new List();
        fruits.forEach(i -> lst.add(i));
        l.setBounds(20, 40, 80, 30);  
        lst.setBounds(20, 70, 80, 80);  
        f.add(l);
        f.add(lst);  
        f.setSize(200,200);  
        f.setTitle("Fruit");   
        f.setLayout(null);   
        f.setVisible(true);  

        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent we) {
                System.exit(0);
            }
        });      
    }    
      
    public static void main(String args[]) {   
        String[] fruits = new String[] { "apple", "berry", "citrus" };
        FruitApp f = new FruitApp(Arrays.asList(fruits));    
    }  
}

Suppose you replace the single-type-import declarations with these module import declarations:

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

You'll get these compile-time errors:

FruitApp.java:8: error: reference to Label is ambiguous
        Label l = new Label("Fruits");
        ^
  both interface java.lang.classfile.Label in java.lang.classfile and class java.awt.Label in java.awt match
FruitApp.java:8: error: reference to Label is ambiguous
        Label l = new Label("Fruits");
                      ^
  both interface java.lang.classfile.Label in java.lang.classfile and class java.awt.Label in java.awt match
FruitApp.java:9: error: reference to List is ambiguous
        List lst = new List();
        ^
  both interface java.util.List in java.util and class java.awt.List in java.awt match
FruitApp.java:9: error: reference to List is ambiguous
        List lst = new List();
                       ^
  both interface java.util.List in java.util and class java.awt.List in java.awt match

The simple name Label is ambiguous. It's contained in java.lang.classfile and java.awt, which the modules java.base and java.desktop export, respectively. This issue applies similarly to the simple name List.

If a declaration of a type (such as a member variable or a parameter name) in a particular scope (such as an inner class or a method definition) has the same name as another declaration in the enclosing scope, then the declaration shadows the declaration of the enclosing scope

To resolve these ambiguities, use single-type-import declarations to specify the canonical names of the simple names used in your code. These declarations shadow the Label and List classes imported by the import module declarations.

import module java.desktop;
import module java.base;
import java.awt.Label;
import java.awt.List;

A declaration shadows another declaration when the shadowing declaration has the same name as another declaration in an enclosing scope. You can refer to a declaration that shadows by its simple name but not the declaration that it shadows. As demonstrated in this example, shadowing can also occur with single-type-import declarations.

Type-import-on-demand declarations can also shadow import module declarations. For example, you can reduce the number of import statements in the FruitApp example by using a type-import-on-demand declarations:

import module java.desktop;
import module java.base;
import java.awt.*;

Implicitly Declared Classes and the java.base Module

Every implicitly declared class (see Creating Simple Source Files) automatically imports, on demand, all public top-level classes and interfaces in all packages exported by the java.base module. It's as if the module import declaration import module java.base appears at the beginning of every implicitly declared class instead of the type-import-on-demand declaration import java.lang.* at the beginning of every ordinary class. For example, you can simplify the FruitMap example with an implicitly declared class as follows:

void main() {
    String[] fruits = new String[] { "apple", "berry", "citrus" };
    Map<String, String> m = Stream
        .of(fruits)
        .collect(Collectors.toMap(
            s -> s.toUpperCase().substring(0,1),
                Function.identity()));
    m.forEach((k, v) ->
        System.out.println(k + " " + v));
}

Importing the java.se Module and Declaring a Transitive Dependence on the java.base Module

You can import the java.se module to import the entire standard Java API, including all the packages contained in the java.base module. Consequently, you can reduce the number of import statements even further in the FruitApp example:

import module java.se;
import java.awt.*;

With this updated list of import statements, compile the FruitApp with the --add-modules java.se option. The java.se module is not part of the default set of root modules for the unnamed module.

Prior to JDK 24, the java.se module didn't contain a transitive dependence on the java.base module. No module could have a transitive dependence on java.base because every module has an implicit dependence on it. However, because module import declarations derive a set of packages to be imported, you can now require the java.base module transitively.