IntelliJ IDEA
IntelliJ IDEA – the IDE for Professional Development in Java and Kotlin
Module Import Declarations: No More Import Hell
Imagine you are proud of yourself for creating an amazing Java application, framework, or a library. You open one of its source files to make some changes and this is what you see:
import java.lang.reflect.*; import java.nio.file.Path; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.stream.Collector; import java.util.concurrent.CompletableFuture; import java.util.concurrent.locks.ReadWiteLock; import java.time.LocalDateTime; // more import statements
While looking at this long list of imports, are you wondering why you need to know the name of every single class or interface used in your class (apart from its configuration)? It seems like a cognitive load to me. Most of the developers I know collapse this section in their IDE and don’t bother expanding it.
The business logic in this source file probably starts after line twenty. Can you do something about it because importing packages won’t help much. Don’t worry, Module Import Declarations can help address this import hell for you.
What is ‘Module Import Declarations’?
Introduced as a preview feature in Java 23 and a production feature in Java 25, Module import declarations enable you to import an entire module using a single import statement. In the above example, you can replace all the import statements with the following line of code, since all those packages are defined as part of the java.base
module:
import module java.base.*;
Yes, just one line of code. The source file can now use List
, Map
, Stream
, Path
, and many more classes. As a developer, you no longer need to think about which package they live in. With this single statement, you can import all of the packages exported by the module java.base
.
Why should you care about this feature?
Apart from the obvious benefits of concise code and escaping from the import hell, this feature helps us developers focus on solving business problems rather than finding the fully qualified names for classes or interfaces to use. Do you think it is worth your time to find out whether the interface Function
is defined in the package java.util.function
or java.util
?. An intelligent IDE such as IntelliJ IDEA helps you to get around it by adding the relevant imports as soon as you use a type in your code (more about it in a later section in this blog post).
It offers convenience for new developers. Imagine you are a new Java developer. Would you prefer having an umbrella import statement so that you need not be bothered about knowing which package defines the commonly used data structures, such as List, Map, or Stream? If you are teaching Java, talking about importing classes interfaces from packages before folks get comfortable using basic data structures could make them lose focus. It would be better for them to get started writing programs that use List and Stream, rather than having them figure out which Java package defines them. Module import declarations help beginners focus on learning Java without getting confused or frustrated by things they don’t need to worry about yet.
It enables faster prototyping. When we are prototyping, we try to get things to work as quickly as possible. In such cases, an umbrella import statement, such as the import module declaration, lets us jump straight into coding. It also keeps the code concise, cleaner and easier to read when we are using large APIs. But this doesn’t imply that you should define your code in a module to use a module import statement.
Before moving forward, let’s cover the IntelliJ IDEA configurations for using this feature.
IntelliJ IDEA Configuration
Import Module declaration was introduced as a preview feature in Java 23 and IntelliJ IDEA has supported this feature since version 2024.3. In soon to be released Java 25, it becomes a production feature (it is now a permanent language feature, and safe to use in your production code).
Java 25 is scheduled to be released in September 2025. To use this feature as a production feature (without needing the --enable-preview
flag during compilation and runtime), you should download the early access version of JDK. You can also do this from IntelliJ IDEA’s settings dialog (Yes! IntelliJ IDEA allows you to download and configure EA versions of the JDK):

In your Project Settings, set the SDK to Java 25. For the language level, select ‘X – Experimental features’ on both the Project and Modules tabs. This enables you to use all Java 25 features, including those currently under review. Java 25 is due to be released in September 2025, and we at JetBrains are working on adding more support. New language options will be accessible in IntelliJ IDEA soon.
More about Module Import Declarations
Knowing why we need a feature is the first step to using it. Let’s look at how to use it and also answer some frequently asked questions from developers about this feature.
Syntax of Module Import Declarations
The syntax is simple. It starts with the keyword import
, followed by module and the name of the module (without a wildcard, that is, *
):
import module java.base.*;
This feature doesn’t work with unnamed modules. Revisit the syntax of this feature: it requires you to name the module you want to import. The example in this section imports the java.base
module.
How to determine the fully qualified name of types in your source code?
Are you wondering how to determine the fully qualified name of a type or variable in your source code when using import module declarations? This was one of the main reasons for using individual imports in source files. This applies to all types of imports, including wildcard and static imports.
Move your cursor to the type in a variable declaration and use the Quick Documentation feature (Ctrl+Q for Win/Linux, or F1 on macOS) to view a documentation popup that shows the package name at the top, followed by the name and its documentation—without having to open another source file in your editor window. You can also use the Quick Definition feature (Ctrl+Shift+I) to view the fully qualified name, along with the definitions of its members, in a popup window. Alternatively, you can hover over the type using your mouse to view the fully qualified name.

On a separate note, if you don’t like showing Quick Documentation popups on mouse hover, you can clear the ‘Show quick documentation on hover’ checkbox in the settings (find it using Shift+Shift). If you want to browse the source code of the used type, use the ‘Go to Declaration or Usages’ feature (Ctrl+B / Cmd+B), which opens the source code in a new editor window.
In the next section, let’s see how to use the import module statement with different libraries or APIs.
How to import JUnit 3.8.1 using import module declaration?
I know it is an old version, but it is still around and in use. How would you import classes and interfaces defined in JUnit 3.8.1 (if you had to) in your source files? To import it using an import module declaration, you must use its module name. The short answer is you cannot because there is no module name for it. JUnit 3.8.1 was created before Java 9 introduced modules.
What about other libraries? How do we find their module names? We usually use build tools, such as Maven, to import dependencies as jar files. If a dependency supports modules, its jar file includes module-info.class (mandatory for modules), which contains the module name. You’ll notice that the JUnit 3.8.1 jar doesn’t include module-info.class.
IntelliJ IDEA users can view module info in External libraries, in the Project Structure window, as shown below:

Of course, you could ask the AI Chat window in IntelliJ IDEA how to find these module names.
Which packages are exported by the modules you import?
Each module defines a module-info.java file that includes information about the packages that module exports to all modules or to specific other modules.
Let’s try to understand this by checking out what happens when you import the module java.base
using IntelliJ IDEA. Click on the module name in the editor or use the relevant shortcut (Go to Declaration or Usages) and you could view the definition of this module to find out all the modules exported by this module. This is shown in the following gif:

The public API in these packages are available to source files that import these modules.
What do you mean by on-demand import?
Developers often argue about importing a single class or interface versus importing all public entities by importing a package using a wildcard character. Which of these do you think is better?
When you import a module or a package using a wildcard (*), you import types on demand. This means you can use any public class, interface, or entity from a package or module without importing each one individually.
Often, developers ask if importing a full module increases the size of their .class files. The short answer is no. On-demand imports make all public classes and interfaces in the module available to use in a source file, but the compiler includes only those that are actually used in your code.
Name conflicts (compilation error)
When you import one or more modules, it is possible that packages within the same module or across modules define classes with the same name. For an example, in the example code below, code at line number 5 (Date date;) won’t compile because importing java.base
module makes java.util.Date
class available, and importing the java.sql module makes java.sql.Date available:
1. import module java.base; // Makes available java.util.Date 2. import module java.sql; // Makes available java.sql.Date 3. 4. public class NameConflicts { 5. Date date; // compilation error 6. }
Let’s assume you want to use the Date class from module java.sql. To address this issue, you could either add a single import statement, as follows:
1. import module java.base; // Makes available java.util.Date 2. import module java.sql; // Makes available java.sql.Date 3. import java.sql.Date; 4. 5. public class NameConflicts { 6. Date date; 7. }
You could also resolve it by using a fully qualified class name, as follows:
1. import module java.base; // Makes available java.util.Date 2. import module java.sql; // Makes available java.sql.Date 3. 4. public class NameConflicts { 5. java.sql.Date date; 6. }
IntelliJ IDEA can highlight name conflicts as you type your code. Invoke context actions to view a list of qualified class names (package name + class name) to choose from, as shown below:

If you are wondering whether it is a good idea to import modules since it could lead to namespace conflicts, these issues can occur with importing packages too. Don’t worry; it results in a compilation error and can be fixed easily.
Also, a module and one of the packages it includes can have the same name. Notice that the module java.sql contains a package java.sql. The fully qualified name of a class or an interface includes its package name, not the name of the module the package is defined in.
Compact Source Files integration
Gavin Bierman mentioned that the idea of this Import Module Declarations came up while the Java team was working on JEP Compact Source Files and Instance main Methods, which aim to reduce the ceremony to learning Java.
Compact source files implicitly import the module java.base
. It implies that the new developers can use any public class or interface from the multiple packages exported by the java.base module, without explicit import statements.
JShell Integration
One of the main benefits of using JShell is that it allows you to quickly evaluate expressions, execute code statements or snippets (and much more) without the ceremony of defining classes.
Prior to Import Module Declarations, JShell automatically imported the ten most used packages (and java.lang) so that developers could easily use those classes or interfaces without explicit import statements. However, you still need import statements for classes or interfaces not defined in these packages, such as the LocalDateTime
class from the package java.time
:

With import module declarations, JShell automatically imports the module java.base. This helps developers use many more classes (such as LocalDateTime
), without needing explicit import statement, as shown in the following image:

One import statement to use the entire standard Java API
By importing the java.se module you can use the entire standard Java API in your source file. Here’s the module information for this module:

The java.se module is an aggregator module. It does not export any package, but requires other modules transitively. In other words, source files importing java.se import the packages exported by modules that are marked required transitive in java.se.
Interview with creators of this feature
We also interviewed the owner of this feature, Gavin Bierman, Programming Language Designer at Oracle.
Gavin mentioned that this feature was co-developed with the JEP Compact Source Files and Instance Main Methods. He covered the differences between single-type imports and type-import-on-demand declarations, explaining what they are and why individuals and organizations prefer one style over the other. He also talked about how the “Module Import Declarations” feature automatically imports on demand from transitive dependencies of modules. He discussed ambiguous imports and how to deal with them, name ambiguity, and how to submit relevant feedback on this feature to the teams at OpenJDK.
Practical Tips
Practical tips and wisdom are always useful when using a feature.
Migrating your codebases to use import module declarations
I’d recommend not replacing all the individual import statements or package statements with import module statements in your codebase. Try module imports in new files or when you’re already touching existing code.
This feature is syntactic sugar; it makes your code more readable but doesn’t offer any performance benefits. The folks who maintain your code are more important than using the latest shiny features in your codebase. If they understand the code in its existing format, respect that.
Group Your Imports
With module imports, Java offers multiple types of imports, such as, importing modules, importing packages, importing classes/interfaces, and static imports of fields or methods. Grouping imports could make them more readable.
IntelliJ IDEA supports importing types as you type them
Whether you are copy-pasting code or typing it, IntelliJ IDEA adds the relevant imports to your codebase, either automatically or by prompting you to select the correct version to import, as shown in the following gif:

Just in case, the preceding settings don’t work for you, ensure you have enabled the relevant import settings in ‘Intentions’ (use IntelliJ IDEA Settings), as follows:

If you haven’t been using this feature in IntelliJ IDEA, you are missing out on a great user experience.
Summary
Introduced as a preview feature in Java 23 and a production feature in Java 25, Module Import Declarations allow importing an entire module with a single import statement.
Apart from the obvious benefits of replacing multiple import statements with a single import, this feature helps us developers escape import hell. It works only with named modules, not unnamed ones or older libraries without module definitions (such as JUnit 3.8.1). Classes and interfaces are imported on demand. Importing a module makes its public classes/interfaces available, but only those actually used are included by the compiler. Importing multiple modules might lead to class name conflicts, requiring specific imports or fully qualified names.
JShell and compact source files automatically import java.base with this feature. If you want to extend it, try importing the java.se module, which provides access to the entire standard Java API. The release of Java 25 is scheduled for September, but IntelliJ IDEA already supports this feature. Try it out and let us know your feedback.
Happy coding.