Third-Party Libraries and Modules: Dealing with Non-Modular JARs

Illustration for Third-Party Libraries and Modules: Dealing with Non-Modular JARs
By Last updated:

One of the biggest hurdles teams face when adopting the Java Platform Module System (JPMS) is integrating third-party libraries that lack module-info.java. Many developers assume such libraries cannot work with JPMS, leading to confusion and delays in migration projects.

The reality is that non-modular JARs can still be used—either through the classpath, as automatic modules, or by repackaging them with explicit modular metadata. Each approach has trade-offs, especially in large-scale enterprise systems where frameworks like Spring, Hibernate, or custom JDBC drivers are common.

Think of non-modular JARs as contractors in a company. They can work alongside employees (modular code), but without contracts (module-info.java), their responsibilities aren’t well-defined. JPMS provides mechanisms to manage them, but careful governance is required.


Approaches to Using Non-Modular JARs

1. Keep Them on the Classpath

  • Simplest approach
  • Works with both modular and non-modular apps
  • Loses modularity benefits (no strong encapsulation or reliable configuration)

Example:

java -cp lib/mylib.jar:mods -m com.example/com.example.Main

2. Use Automatic Modules

  • Place JAR on the module path → becomes an automatic module
  • Exports all packages and requires all others
  • Module name derived from JAR filename
java --module-path mods:lib      -m com.example/com.example.Main

mylib-1.0.jar → module mylib

In module-info.java:

module com.example {
    requires mylib;
}

3. Add Automatic-Module-Name to MANIFEST

  • Stabilizes module names across versions
  • Recommended for libraries not yet modularized

Manifest entry:

Automatic-Module-Name: com.example.mylib

4. Repackage with Explicit module-info.java

  • Create a modular JAR manually or with tools like moditect
  • Provides full JPMS benefits (encapsulation, dependency management)

Example:

module com.example.mylib {
    exports com.example.mylib.api;
}

Pitfalls

❌ Relying on automatic module names (unstable, may change per release)
❌ Overusing ALL-UNNAMED or --add-opens (bypasses modular security)
❌ Mixing classpath and module path (causes visibility issues)
❌ Ignoring migration path for libraries (technical debt grows)


Best Practices

✅ Prefer libraries that publish modular JARs
✅ If unavoidable, use Automatic-Module-Name for stability
✅ Use jdeps to analyze dependencies
✅ Gradually migrate critical libraries with explicit module-info.java
✅ Keep classpath use minimal in modular projects


Example: Mixed Modular and Non-Modular App

Project Structure

mods/com.example.app
lib/legacy-lib.jar

Command

java --module-path mods:lib      -m com.example.app/com.example.app.Main

Here, legacy-lib.jar is treated as an automatic module.


What's New in Java Versions?

  • Java 5–8 → N/A (no modules)
  • Java 9 → JPMS introduced, non-modular JARs supported via automatic modules
  • Java 11 → Common use of Automatic-Module-Name manifest entry
  • Java 17 → Ecosystem shift: more libraries shipping modular JARs
  • Java 21 → No significant updates across Java versions for this feature

Real-World Analogy

Non-modular JARs are like freelancers without contracts. They can still work, but roles and boundaries are unclear. Defining contracts (module-info.java) ensures stability and accountability in large teams.


Summary & Key Takeaways

  • Non-modular JARs can be used with JPMS via classpath, automatic modules, or repackaging
  • Automatic modules export everything and require everything—use carefully
  • Automatic-Module-Name stabilizes names across versions
  • Best long-term solution: modularize libraries explicitly
  • Migration tools like jdeps and moditect help accelerate the process

FAQ: Non-Modular JARs and JPMS

1. What is the difference between classpath and module path?
Classpath loads everything blindly; module path enforces explicit dependencies.

2. Why do I get “package is not visible” errors?
Because the library isn’t modularized or properly opened to reflection.

3. What is the purpose of requires transitive?
It lets downstream modules inherit dependencies but should be used sparingly.

4. How do open and opens differ for non-modular JARs?
Non-modular JARs on classpath are open by default; automatic modules behave as open too.

5. What are automatic modules, and should I use them?
They let non-modular JARs act as modules. Use them temporarily, not as a long-term solution.

6. How does JPMS improve security compared to classpath?
It prevents accidental use of internal APIs by enforcing encapsulation.

7. Should I use jlink or jmod with non-modular JARs?
Prefer modular JARs. Non-modular ones may limit custom runtime optimizations.

8. Can I migrate legacy projects incrementally with non-modular JARs?
Yes, start with automatic modules, then modularize step by step.

9. How do I handle third-party libraries that aren’t modularized?
Use automatic modules or repackage with module-info.java.

10. Do frameworks like Spring and Hibernate fully support JPMS?
Yes, but they may require --add-opens for reflection-heavy features.