After studying this material, students should be able to:
Traditional Java Development:
┌─────────────────────────────────────┐
│ Source Files → Compile → JAR files │
│ Runtime: -classpath jar1:jar2:jar3 │
│ Everything dumped into one flat │
│ namespace (ALL-UNNAMED) │
└─────────────────────────────────────┘
| Problem | Consequence |
|---|---|
| No explicit dependency boundaries | Any code can access any other code |
ALL-UNNAMED module access | Hides technical debt sources |
| No version information in descriptors | Dependency versions managed separately |
| Poor CLI tooling | AI agents struggle to navigate Java projects |
# This kind of flag became dangerously common:
--add-opens java.base/java.lang=ALL-UNNAMED
# Problems:
# ✗ Hides WHICH module is actually requesting access
# ✗ Bypasses encapsulation for everyone
# ✗ Becomes a security liability as Java moves toward
# "Integrity by Default"
Think of it like this: ALL-UNNAMED is like giving a master key to an entire apartment building instead of one specific tenant
A module-info.java file that explicitly declares:
// Traditional module-info.java
module com.example.application {
requires com.example.framework;
exports com.example.application.api;
}
Module System Arrived → Build Tools Already Mature
↓
Module descriptor became "just another file"
to keep synchronized with build files (pom.xml, etc.)
↓
Two sources of truth = maintenance burden
The ja tooling extends module descriptors using documentation tags:
/**
* @mainClass com.example.application.Main ← Entry point metadata
*/
module com.example.application {
requires com.example.framework; // @1.2.3 ← Version pinned inline
}
/**
* @mainClass com.example.application.Main
* ↑
* Documentation tag carries project metadata
*/
module com.example.application {
↑
Reverse-DNS naming convention (like packages)
requires com.example.framework; // @1.2.3
↑ ↑
Dependency Version as comment
}
Before: After:
┌─────────────────┐ ┌──────────────────────┐
│ module-info.java│ │ module-info.java │
│ pom.xml │ → │ (single source of │
│ build.gradle │ │ truth for everything)│
└─────────────────┘ └──────────────────────┘
ja Tool Family┌─────────────────────────────────────────────────────┐
│ ja (orchestrator) │
│ Provides CLI ergonomics + coordination │
└──────────┬──────────────────────────────────────────┘
│ discovers and delegates to:
┌──────┴────────────────────────────────┐
│ │
┌───▼────┐ ┌─────┐ ┌──────┐ ┌────────┐ │
│ jig │ │ dep │ │ doc │ │ other │ │
│(proxy) │ │tool │ │ tool │ │ tools │ │
└────────┘ └─────┘ └──────┘ └────────┘ │
└──────────────────────────────────────┘
Each tool: standalone, composable, implements Tool/ToolProvider
// Tools implement standard Java interfaces:
public class MyTool implements Tool {
// Can be run IN-PROCESS (no subprocess overhead)
// Discoverable by the platform
// Installable alongside standard JDK tools
}
# You can use ja as full orchestrator:
ja require com.example.framework@1.2.3
# OR compose individual tools directly:
jig resolve com.example.framework@1.2.3
dep check module-info.java
doc generate --module com.example.app
Analogy: Think of Unix philosophy — small tools that do one thing well and can be piped together
1,000 Most Popular Maven Artifacts:
┌─────────────────────────────────────────┐
│ ████████░░░░░░░░░░░░░░ 232 (23.2%) │
│ Explicit module definitions │
│ │
│ ████████░░░░░░░░░░░░░░ 248 (24.8%) │
│ Automatic module names declared │
│ │
│ ████████████████████░░ 520 (52%) │
│ No module name at all │
└─────────────────────────────────────────┘
Module System Rule:
Module Name = Namespace
Problem:
"jackson-databind" artifact → what's the module name?
com.fasterxml.jackson.databind? jackson.databind? other?
Format: pkg:maven/{group}/{module-name}
Example: pkg:maven/com.netflix/com.netflix.tools.ja
↑ ↑
Verified DNS Full module name
namespace
1. Explicit module definition in artifact?
→ Use it directly ✓
2. Author published relocation POM at canonical coordinate?
→ Follow redirect ✓
3. Neither available?
→ Walk namespace from DNS root using Maven conventions
→ Infer coordinates from module name ✓
4. Popular module with non-DNS name?
→ Check bundled alias list ✓
jig Module Proxyjig presents all modules using filename-based naming conventions
↓
Even automatic modules without stable names become safe to use
# Current reality (problematic):
java --add-opens java.base/java.lang=ALL-UNNAMED \
--enable-native-access=ALL-UNNAMED \
-jar myapp.jar
# Problems:
# ✗ Blanket permission to everything unnamed
# ✗ No audit trail of who needs what
# ✗ Violates "Integrity by Default" direction
Step 1: Library DECLARES what access it needs
↓
Step 2: Application EXPLICITLY AUTHORIZES it
↓
Step 3: Resolution FAILS if authorization missing
// LIBRARY declares its requirements:
/**
* @enableFinalFieldMutation com.example.framework
* ↑
* "I need to mutate final fields — here's who I am"
*/
module com.example.framework {
// module body
}
// APPLICATION must explicitly authorize:
/**
* @mainClass com.example.application.Main
* @enableFinalFieldMutation com.example.framework
* ↑
* "I knowingly authorize THIS specific module"
*/
module com.example.application {
requires com.example.framework; // @1.2.3
}
# CLI adds both dependency and authorization together:
ja require com.example.framework@1.2.3 \
--enable-final-field-mutation com.example.framework
# Without authorization → resolution FAILS with clear error
# ✓ No silent permission grants
# ✓ Full audit trail in module descriptor
# ✓ Code review can see all authorizations
| Annotation Tag | Purpose |
|---|---|
@enableFinalFieldMutation | Allow modifying final fields |
@enableNativeAccess | Allow native/foreign memory access |
| Qualified exports/opens | Fine-grained package-level access |
Resolution Time: Subsequent Runs:
┌──────────────────┐ ┌──────────────────────┐
│ Resolve deps │ │ Read hashes from │
│ Compute hashes │ → → → │ module-info.hash │
│ Store in │ │ Verify each artifact │
│ module-info.hash │ │ Reject if changed ✗ │
└──────────────────┘ └──────────────────────┘
# module-info.hash (conceptual):
com.example.framework@1.2.3 = sha256:a3f8b2...
com.example.other@2.0.0 = sha256:9c4d1e...
Why this matters: Protects against supply chain attacks where a published artifact is silently replaced
Traditional: New Approach:
┌─────────────────────┐ ┌──────────────────────────┐
│ Annotation processor│ │ Annotation processing = │
│ runs silently during│ → │ EXPLICIT code gen step │
│ compilation │ │ Generated sources visible │
│ Output hidden │ │ in code review │
└─────────────────────┘ │ Module assemblable WITHOUT│
│ running generator code │
└──────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Modern Java Module Development │
├─────────────────────────────────────────────────────────────┤
│ │
│ module-info.java = SINGLE SOURCE OF TRUTH │
│ ├── Module name (reverse DNS) │
│ ├── Dependencies + versions (inline comments) │
│ ├── Metadata (doc tags: @mainClass, etc.) │
│ ├── Access authorizations (@enableNativeAccess, etc.) │
│ └── Integrity hashes (module-info.hash companion) │
│ │
│ TOOLING │
│ ├── ja: CLI orchestrator │
│ ├── jig: module proxy for discovery │
│ └── Composable standalone tools (Tool/ToolProvider) │
│ │
│ DISCOVERY │
│ ├── Canonical coordinates: pkg:maven/{group}/{module} │
│ ├── DNS-verified namespaces │
│ └── Fallback inference strategies │
│ │
│ SECURITY │
│ ├── Explicit access authorization (no ALL-UNNAMED) │
│ ├── Hash-based artifact integrity │
│ └── Visible annotation processing │
│ │
└─────────────────────────────────────────────────────────────┘
Q1: Why is ALL-UNNAMED considered harmful?
It grants blanket access without identifying which module needs it, hiding technical debt
Q2: What makes the new module descriptor a "complete" project description?
It combines module structure, dependency versions, entry point metadata, and access authorizations in one file
Q3: What happens if an application doesn't authorize a library's declared access requirement?
Dependency resolution fails with an explicit unsatisfied access requirement error
Q4: How does jig help with the 52% of artifacts that have no module name?
It presents resolved modules using filename-based naming conventions, making even automatic modules safe to use