When developers first migrate to the Java Platform Module System (JPMS), a common misconception is that modularization automatically improves performance. In reality, poorly designed modules may introduce overhead, such as slower startup times, redundant dependencies, or reflection bottlenecks. Others fear that modules slow applications down, leading them to avoid modularization entirely.
Understanding the performance implications of modular applications is essential in real-world systems. From enterprise applications with thousands of classes to microservices packaged as custom runtimes, performance impacts influence scalability, resource usage, and end-user experience.
Think of modules as shipping containers: when well-organized, they streamline transport and reduce costs. But when mislabeled or overloaded, they delay delivery. The same applies to modular applications: correct design yields efficiency, while poor practices hurt performance.
Positive Performance Impacts of JPMS
1. Faster Startup with jlink
By creating custom runtime images using jlink
, applications start faster because they load only the required modules.
jlink --module-path $JAVA_HOME/jmods:out --add-modules com.example.app --output custom-runtime
2. Reduced Memory Footprint
Smaller runtimes mean fewer unused classes in memory, improving memory efficiency.
3. Improved Security = Indirect Performance
Less exposure to reflection reduces runtime overhead from security checks and unauthorized reflective calls.
4. Dependency Graph Optimization
JPMS enforces explicit dependencies, reducing classpath scanning overhead and improving reliability.
Negative Performance Considerations
1. Startup Overhead
Module resolution at startup may add overhead compared to flat classpath applications, especially in very large systems.
2. Reflection Limitations
Frameworks (like Spring or Hibernate) that rely on reflection may require opens
, which can reintroduce overhead.
3. Complexity in Build Pipelines
Incorrect module boundaries can lead to redundant dependencies and larger runtimes, increasing memory and disk usage.
Best Practices for Performance
✅ Use jlink
to create optimized runtimes
✅ Minimize module count to reduce resolution overhead
✅ Avoid unnecessary requires transitive
declarations
✅ Limit reflection with targeted opens
instead of open
✅ Use jdeps
to analyze and trim dependencies
✅ Profile modular applications regularly with JFR or similar tools
❌ Avoid automatic modules in production (can bloat dependency graph)
❌ Don’t export internal APIs unnecessarily (may trigger extra loading)
❌ Don’t over-split into too many small modules (increases resolution time)
Example: Optimizing a Modular Application
module-info.java
module com.example.analytics {
exports com.example.analytics.api;
requires transitive com.example.utils; // expose only if clients need it
opens com.example.analytics.dto to com.fasterxml.jackson.databind; // controlled reflection
}
Optimization Steps
- Use
jdeps
to verify dependencies - Bundle with
jlink
→ smaller runtime - Profile startup with
--show-module-resolution
- Avoid exporting
impl
packages
What's New in Java Versions?
- Java 5–8 → N/A (Modules introduced in Java 9)
- Java 9 → Introduction of JPMS and
jlink
- Java 11 → Tooling refinements and improved
jdeps
analysis - Java 17 → Performance improvements in module resolution and startup
- Java 21 → No significant updates across Java versions for this feature
Real-World Analogy
Running modular applications is like packing a travel bag. A carefully chosen set of essentials (modules) makes the trip light and fast. Overpacking (exporting everything or using automatic modules) makes the trip heavy and slow.
Summary & Key Takeaways
- JPMS can reduce runtime size and memory footprint
jlink
enables faster startup and optimized runtimes- Poorly designed modules can hurt performance
- Avoid pitfalls like automatic modules and unnecessary exports
- Use tools like
jdeps
and profiling to continuously optimize
FAQ: Performance and JPMS
1. Does JPMS always improve performance?
Not automatically. Benefits come from good design and tools like jlink
.
2. Why does my modular app start slower?
Because module resolution adds overhead. Use jlink
to mitigate it.
3. How does jlink
improve startup performance?
It creates a smaller runtime with only the modules you need.
4. What is the impact of requires transitive
on performance?
It may unnecessarily expose dependencies, leading to larger runtimes.
5. Do automatic modules affect performance?
Yes, they can bloat the dependency graph and slow resolution.
6. How does JPMS improve memory usage?
By limiting loaded modules, fewer classes are kept in memory.
7. Should I split into many small modules for performance?
No, too many modules can slow startup. Balance cohesion and size.
8. How does reflection affect modular performance?
Excessive use of opens
reintroduces reflection overhead.
9. Can I profile modular apps differently than classpath apps?
No, profiling tools like JFR work the same, but you must consider module resolution.
10. Do frameworks like Spring perform worse with modules?
They may require adjustments (e.g., opens
), but performance impact is usually minimal when configured correctly.