This article introduces 'ja' and its composable command-line tools designed to leverage the Java Module System, moving beyond the traditional classpath. It focuses on how these tools enhance dependency management, ensure module integrity through explicit authorization and persistent hashing, and streamline development for modern Java applications and agent-based coding. The tools aim to improve security, reliability, and developer experience by making modules a default for Java projects.
Read original on Netflix Tech BlogThe Java ecosystem has long relied on a mature build and dependency management model, primarily through the classpath. However, the introduction of the Java Module System (JMS) presents an opportunity to evolve this model. The article highlights that while existing tools served well, JMS enables a more robust and secure approach to application development by formalizing module definitions and dependencies. This shift is critical for large-scale, complex applications, impacting how system architects consider dependency resolution and application boundaries.
Netflix introduces 'ja', a suite of composable command-line tools built on the Java Module System. These tools aim to modernize the Java development experience, particularly for environments where graphical IDEs are not always optimal, such as agent-based coding. The architecture emphasizes standalone, interoperable tools like `jig` for module resolution and assembly, `jfmt` for source formatting, `jist` for symbol search, and `jdocserver` for API documentation. This composable design allows developers to pick and choose tools, promoting flexibility and integration into various build pipelines.
/**
* @mainClass com.example.application.Main
*/
module com.example.application {
requires com.example.framework; // @1.2.3
}Architectural Principle: Explicit Access Control
One of the key architectural benefits of the Java Module System is its emphasis on integrity and explicit access control. Moving away from the 'ALL-UNNAMED' classpath approach, modules enforce clear boundaries and require explicit authorization for sensitive operations like mutating final fields or accessing native code.
The 'ja' tools extend this principle by allowing runtime access requirements to be declared as module metadata. For instance, a module needing to mutate final fields must explicitly declare this, and the consuming application must explicitly authorize it. This model prevents unintended access and enhances the security posture of applications. Furthermore, module integrity is ensured through persistent hashes of resolved binary dependencies, which are verified upon subsequent resolution, rejecting any altered artifacts. This is a critical feature for maintaining supply chain security in large distributed systems.
This explicit declaration and authorization model is supported through custom documentation tags and command-line arguments:
/**
* @mainClass com.example.application.Main
* @enableFinalFieldMutation com.example.framework
*/
module com.example.application {
requires com.example.framework; // @1.2.3
}
ja require com.example.framework@1.2.3 \
--enable-final-field-mutation com.example.frameworkThe article acknowledges the challenge of integrating a module-first approach with the vast existing Maven Central ecosystem, where many artifacts lack explicit module definitions. 'ja' addresses this by using Maven's established conventions (reverse domain group IDs) to derive canonical Maven module coordinates. It also provides mechanisms for discovering modules, including handling automatic modules and bundling aliases for popular modules. The `jig` tool includes a module proxy to present resolved modules using filename-based conventions, ensuring compatibility and stability for existing artifacts.