Running on Java 26+35-2893 (Preview)
Home of The JavaSpecialists' Newsletter

337Module Imports

Author: Dr Heinz M. KabutzDate: 2026-08-31Java Version: 25Sources on GitHubCategory: Language
 

Abstract: Module imports are a new feature in Java 25 and are like a wildcard import for *all* the types in a module. If you hate wildcard imports, you'll despise this even more. But if, like me, you don't really care whether someone uses a wildcard or single-type import, this is for you.

 

Welcome to the 337th edition of The Java(tm) Specialists' Newsletter. The last few months have been crazy productive for me. Programming has not been this much fun since Turbo Pascal, although sometimes waiting for the AI Agents to do their thing, with their prompting for input, reminds me of installing Windows 3.1 from half a dozen floppy disks. IYKYK.

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

Module Imports

The torrent of Java projects that I've done in the last 4 months began with a prompt that I sent to GitHub Copilot. It went like this:

I want to create a website that allows me to log my intermittent fasting start and finish each day. It should have a button for "START EATING", and if we are in eating state it should change that to "STOP EATING". When we are in fasting state, it should also have another button for "EXTEND EATING" in case I change my mind. I want to have a MySQL table for the "mean intermittent fasting" times, stored as UTC, but the website should always show local time of the browser, based on location of where I am, but ignoring VPNs. The SQL queries should be handled inside the Database class.

That was the start. I wanted Copilot to create a secret page on my existing JavaSpecialists.eu website, where I could log my intermittent fasting times. I found intermittent fasting to be the easiest diet to get consistent results. I even had a name for it - Mean-I-F - for Mean (as in Average) Intermittent Fasting, plus of course the play on words of "Mean A.F.". The idea of averaging came from my university friend. As teenagers, we would goad each other into seeing who could finish a family sized pizza first - and now look at us! The challenge with not eating is that we are social animals and food is a way to connect. By averaging the time over a week, we can still hang out with friends on occasion. It works amazingly well.

Since that first prompt, I've refined Mean-I-F to integrate with my Garmin watch and bathroom scale, my Oura Ring, my iHealth blood pressure meter, and several other gadgets. I pulled together data from all over my disk and many online services and spreadsheets, and stuck them all into one database. I created an MCP server, so that I can connect with Perplexity and ask the question: "How did I do this month compared to the same month in 2023?" [Perplexity: August 2026 shows clear progress: you're 4+ kg lighter, sleeping ~20 minutes more per night, and moving significantly more (especially walking daily instead of sporadically). The daily running streak maintained across both years is impressive, but the added walking and swimming volume this year shows a more well-rounded fitness routine.]

This Mean-I-F project alone has become so complex that it would have taken a good programmer years to complete if they coded everything by hand like a caveman.

And that's not all. I've created so many useful (to me) projects in the last few months that I sometimes forget what I've done.

I'm sure that we've all had similar experiences coding with AI Agents.

And yet ...

At some point, I made the mistake of looking at the Java code that was being produced. Sometimes, things would not work, and I would not see any errors in the logs. I then found dozens of catch (Exception ignored) {} and throws Exception in the code base. I spent a frustrating day trying to coax Copilot to fix this mess. By this time, the code had grown into hundreds of classes, incorporating 2FA, an MCP server, several integrations with devices, including calls to Rod Johnson's Embabel. And after eventually resorting to IntelliJ inspections to weed out all these code smells, at the next code gen, it immediately reverted back to littering my codebase with this filth. Yes, I've now added copilot-instructions.md that will hopefully reign it in a bit, but since it is a bit (ok a lot) non-deterministic, it needs a firm hand.

One of the things that I have ignored for decades are Java import statements. I've always found the debate for and against wildcard imports rather quaint. If I'm going to use an ArrayList, chances are high that I will also need a List and maybe an Iterator or a Stream. OK, confession time: In my own projects, I use wildcard imports extensively and I fold the imports away in my IDE and never look at them... There, I've said it. I just don't want to see dozens of single imports all from the same package.

Something else. A few years ago, a customer wanted to upskill their programmers to modern Java. One of the things we covered was how JPMS (or also called Java Modules) works. Since then, I've tried to use Java Modules whenever I could. Even my Java Dynamic Proxies Book uses them. They help to enforce a cleaner interface between components, resulting in better decoupling and neater abstractions. You'll be horrified how mangled some of the modules became under the steady gaze of the LLMs. I know that Java Modules don't solve all problems. But they do have some neat features.

And here is one more reason to think about using them. Now I admit that I haven't added JPMS to all modules in my Mean-I-F system yet. Since a lot of programmers do not know how it works, and the training data for the LLMs is a bit sparse, I think it will require a bit of manual labour to get right. However, one thing which is really great is the import module new language structure in Java 25. We can finally put the debate of "wildcard or not" to rest, and just say import module java.base; and breathe a sigh of relief.

Here is my FitnessAuth1 class before:

    
package com.meanif.fitness.web;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

/**
 * Password hashing and verification utilities for
 * fitness-application user accounts.
 */
public final class FitnessAuth1 {
    {
        // *snip*
    }
}

You can see in the FitnessAuth1 that we've diligently used single-class imports for all the classes. And this is what it looks like with the new import module ... statement:

    
package com.meanif.fitness.web;

import module java.base;

/**
 * Password hashing and verification utilities for
 * fitness-application user accounts.
 */
public final class FitnessAuth2 {
    {
        // *snip*
    }
}

Fortunately most of the classes have a distinct name. There are a few exceptions. For example, we have java.time.Duration in the java.base module and javax.xml.datatype.Duration in the java.xml module. If we import module java.base and import module java.sql, then the java.sql module will transitively pull in the java.xml module and we have an ambiguity. A similar case is the clash between java.util.Date and java.sql.Date. Fortunately clashes of simple class names within a module are rare. Most of the 10 clashes within java.base have to do with the java.security.cert and javax.security.cert packages. In my Mean-I-F program, the only time I had a clash within java.base was with the dynamic Proxy class, which shadows the java.net.Proxy. In that case, we simply have to add an explicit import:

    
package com.meanif.wellness.coach;

import module java.base;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import com.meanif.fitness.model.stats.WellnessHistoryStats;
import com.meanif.fitness.model.stats.WellnessHistoryStatsProvider;
import com.meanif.wellness.model.WellnessReportPurpose;
import java.lang.reflect.Proxy;
import org.junit.Test;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
import org.springframework.ai.chat.metadata.DefaultUsage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;

public class WeeklyWellnessReportEngineCostTest {
    @Test
    // *snip*
}

What about the other classes? As I mentioned above, I have not modularized all of my components yet, and so I cannot yet say import module com.meanif.fitness.model. Spring Framework is also not modularized. They have gone down the path of stable automatic modules instead of explicit. They thus never have a module-info.java file. Spring Framework uses a lot of deep reflection, dynamic proxies, etc. and they want the flexibility of the classpath. It is unlikely that they will ever be properly modularized as in JPMS. Now it would be possible to refer to Spring Framework using the automatic modules inside our module-info.java file with requires. For example, Spring Boot would be added to our module-info.java with requires spring.boot;. We could then say import module spring.boot;. It doesn't win us that much though. The more modules we import, the higher our chance of ambiguous classes.

Here is a class in my project with a lot of single import statements:

    
package eu.javaspecialists.tjsn.issue337;

// module org.apache.tomcat.embed.core:
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

// module org.slf4j:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// automatic module spring.beans:
import org.springframework.beans.BeansException;

// automatic module spring.context:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

// automatic module spring.core:
import org.springframework.core.annotation.Order;

// automatic module spring.security.core:
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;

// automatic module spring.security.config:
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;

// automatic module spring.security.core:
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

// automatic module spring.security.crypto:
import org.springframework.security.crypto.password.PasswordEncoder;

// automatic module spring.security.web:
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;

// automatic module spring.web:
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UriComponentsBuilder;

// automatic module spring.webmvc:
import org.springframework.web.servlet.support.RequestContextUtils;

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class MeanIFSecurityConfiguration1 {
    // *snip*
}

If we create a module-info.java file, we can use import module, like so:

    
package eu.javaspecialists.tjsn.issue337;

import module org.apache.tomcat.embed.core;
import module org.slf4j;
import module spring.beans;
import module spring.context;
import module spring.core;
import module spring.security.config;
import module spring.security.core;
import module spring.security.crypto;
import module spring.security.web;
import module spring.web;
import module spring.webmvc;

// name clashes - need explicit imports:
import org.slf4j.Logger;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.core.Authentication;

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class MeanIFSecurityConfiguration2 {
    // *snip*
}

We could define an aggregate JPMS module to make imports and module dependencies easier, similarly to how java.se works. This module would contain only one file - module-info.java. This would transitively make all the Spring modules available. It might make sense to have several such aggregate modules for different profiles: AI, Web, Security, etc. Here is one example of such a module:

    
module com.meanif.spring.imports {
    requires transitive org.apache.tomcat.embed.core;
    requires transitive org.slf4j;
    requires transitive spring.beans;
    requires transitive spring.context;
    requires transitive spring.core;
    requires transitive spring.security.config;
    requires transitive spring.security.core;
    requires transitive spring.security.crypto;
    requires transitive spring.security.web;
    requires transitive spring.web;
    requires transitive spring.webmvc;
}

Our own module-info.java file now becomes super simple:

    
module eu.javaspecialists.tjsn.issue337 {
    requires transitive com.meanif.spring.imports;
}

And our class also becomes a lot simpler:

    
package eu.javaspecialists.tjsn.issue337;

import module com.meanif.spring.imports;

import org.slf4j.Logger;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.core.Authentication;

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class MeanIFSecurityConfiguration3 {
    // *snip*
}

Unfortunately, I cannot use this technique in my Mean-I-F project yet, because I first have to modularise all my components. Maybe one day, but I have so many interesting projects going on at the same time, that will have to wait. In the meantime, I have used import module java.base; wherever I could.

Kind regards

Heinz

P.S. If you are interested in learning how techniques like Java Modules work, have a look at my Java Specialists Superpack, which contains courses on modern Java.

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack

Java Specialists Superpack Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...