Free book sample

Modular Java

Building Scalable Applications with the Java Module System

What if your Java application could be organized into independent, self-contained units that communicate through well-defined interfaces?

207 pages9 chaptersDec 17, 2025Java

Download sample:

About this book

The Java Module System transforms how you structure code, making applications easier to maintain, test, and scale. This book guides you through building modular Java applications that reduce complexity and improve code quality. You'll learn how to design modules that enforce strong encapsulation, manage dependencies effectively, and create architectures that grow with your needs. Whether you're refactoring legacy code or building new systems, you'll discover practical strategies for implementing modules in real-world scenarios. From understanding module declarations to integrating with your build pipeline, this comprehensive guide provides the knowledge and techniques needed to master modularization on the JVM and write cleaner, more maintainable code.

Table of contents

  1. IntroductionWhy Modularization Matters in Modern Java, The Evolution of Java and the Module System, What You'll Learn in This Book, How to Use This BookRead below
  2. Chapter 1: Understanding the Java Module SystemModule Basics and Core Concepts, Modules vs. Packages: Key Differences, The Module Declaration File (module-info.java), Types of Modules: Named, Automatic, and Unnamed
  3. Chapter 2: Designing Modular ArchitectureCore Principles of Modular Design, Identifying and Defining Module Boundaries, Managing Dependencies Between Modules, Preventing Circular Dependencies
  4. Chapter 3: Encapsulation and ExportsAchieving Strong Encapsulation, Using Exports and Requires Declarations, Qualified Exports and Transitive Dependencies, Benefits of Proper Encapsulation
  5. Chapter 4: Building and Compiling Modular ApplicationsCompiling Modular Code, Understanding the Module Path, Integration with Maven and Gradle, IDE Support for Modular Development
  6. Chapter 5: Testing Modular ApplicationsTesting Strategies for Modular Code, Testing Module Boundaries and Interfaces, Creating and Organizing Test Modules, Testing Tools and Frameworks for Modules
  7. Chapter 6: Migration Strategies for Existing ApplicationsPlanning Your Migration to Modules, Incremental Migration Strategies, Handling Legacy Code and Dependencies, Common Migration Challenges and Solutions
  8. Chapter 7: Advanced Module FeaturesOpen Modules and Reflection, Service Providers and Service Locators, Module Layers and Dynamic Configuration, Version Selection and Module Resolution
  9. Chapter 8: Performance and OptimizationImproving Startup Performance with Modules, Memory Efficiency and Class Loading, Profiling and Monitoring Modular Applications, Optimization Techniques and Best Practices
  10. Chapter 9: Real-World Modularization PatternsImplementing Layered Architecture with Modules, Modules in Microservices and Distributed Systems, Building Plugin Systems with Modules, Case Studies: Modularization in Practice
  11. ConclusionKey Takeaways and Next Steps, The Future of Java Modules
  12. Glossary
  13. Further Reading and Resources

Introduction

Software complexity grows faster than most developers anticipate. What starts as a clean, manageable codebase can quickly become a tangled web of dependencies, unclear boundaries, and hidden coupling. As Java applications scale, maintaining code quality becomes increasingly challenging. Classes reach into unexpected corners of the system, dependencies multiply without clear justification, and refactoring becomes risky because the impact of changes is difficult to predict. The Java Module System addresses these challenges by providing a structured approach to organizing code into explicit, self-contained units with well-defined boundaries.

The introduction of modules in Java 9 represented one of the most significant changes to the platform since its inception. This wasn't merely a new feature added to the language—it fundamentally changed how Java applications can be structured and deployed. The module system provides mechanisms for strong encapsulation, explicit dependency management, and improved performance characteristics. For developers working with large codebases or building applications that need to scale, understanding and implementing modularization has become essential. The benefits extend beyond code organization to include faster startup times, reduced memory footprint, and clearer architectural boundaries that make systems easier to understand and maintain.

This book provides a comprehensive guide to building modular Java applications. Whether you're starting a new project or considering how to refactor an existing system, you'll find practical strategies and concrete examples that demonstrate how to apply modularization effectively. The focus throughout is on real-world application—not just understanding the syntax of module declarations, but learning how to design module boundaries, manage dependencies, test modular code, and integrate modules into your development workflow. By the end of this book, you'll have the knowledge and confidence to build Java applications that are more maintainable, scalable, and aligned with modern software engineering practices.

Why Modularization Matters in Modern Java

Traditional Java applications rely on packages and the classpath to organize code, but these mechanisms have significant limitations. The classpath is essentially a flat list of JAR files where all public classes are accessible to all other code. This creates an environment where encapsulation exists only at the class level—you can make individual classes or methods private, but you cannot hide entire packages or control which parts of your library are intended for external use versus internal implementation details. As applications grow, this lack of higher-level encapsulation leads to unintended dependencies, where code in one part of the system reaches into implementation details of another part, creating tight coupling that makes refactoring difficult and risky.

The module system introduces a new level of organization above packages. A module is a named, self-describing collection of code and data that explicitly declares its dependencies and what it makes available to other modules. This explicit declaration creates strong encapsulation—you can have public classes that are only accessible within your module, not exposed to the outside world. Consider a logging framework: you might have public classes for internal coordination between different parts of the framework, but only want to expose a simple API to users. Without modules, all public classes are visible. With modules, you can export only the API package while keeping implementation packages hidden, even if they contain public classes.

Modularization directly addresses several critical challenges in modern software development:

  • Dependency management: Modules make dependencies explicit and verifiable at compile time, preventing runtime classpath errors
  • Encapsulation: Implementation details can be truly hidden, reducing coupling and making APIs clearer
  • Scalability: Well-defined module boundaries make it easier to understand, modify, and extend large codebases
  • Performance: The JVM can optimize module loading and reduce memory usage by loading only required modules
  • Security: Reduced surface area for attacks through better encapsulation and explicit access control

Real-world evidence demonstrates the value of modularization. The JDK itself was modularized in Java 9, breaking the monolithic rt.jar into over 90 modules. This restructuring enabled the creation of custom runtime images containing only the modules an application needs, reducing the JDK footprint from over 200MB to as little as 30-40MB for simple applications[1]. Applications benefit from faster startup times and reduced memory consumption. Beyond performance, the modular JDK provides clearer boundaries between different parts of the platform, making it easier to understand what functionality depends on what, and enabling more aggressive evolution of internal APIs without breaking compatibility.

For application developers, modularization offers similar benefits at the application level. Consider a typical enterprise application with layers for presentation, business logic, and data access. Without modules, code in the presentation layer can directly access data access classes, bypassing business logic and creating maintenance problems. With modules, you can enforce architectural boundaries—the presentation module can only depend on the business logic module, which in turn depends on the data access module. The compiler enforces these boundaries, preventing violations that would otherwise only be caught through code review or runtime errors. This architectural enforcement becomes increasingly valuable as teams grow and more developers work on different parts of the system.

The shift to modular thinking also changes how you approach library design and API evolution. When you design a module, you must explicitly decide what to expose and what to keep internal. This forces clearer thinking about your API surface and what commitments you're making to users of your code. Internal refactoring becomes safer because you know exactly what code is exposed and what can be changed freely. Versioning and compatibility become more manageable because the boundaries between what's public and what's private are explicit and enforced. For teams building reusable libraries or frameworks, this clarity is invaluable for maintaining backward compatibility while continuing to evolve implementation details.

Modularization also aligns with modern architectural patterns. Microservices architectures emphasize loose coupling and clear boundaries between services—modules provide similar benefits within a single JVM. Domain-driven design advocates for bounded contexts with explicit interfaces—modules provide a mechanism to enforce these boundaries in code. Even monolithic applications benefit from internal modularization, creating what's sometimes called a "modular monolith" that provides many benefits of microservices without the operational complexity of distributed systems. The module system gives you tools to implement these architectural patterns with compiler-enforced boundaries rather than relying solely on discipline and convention.

The Evolution of Java and the Module System

Java's journey toward modularization spans over a decade of development and reflects fundamental challenges in managing large-scale software systems. The origins trace back to 2005 when Sun Microsystems began Project Jigsaw, an ambitious effort to modularize the JDK itself. The project faced numerous technical challenges and delays, missing the Java 7 and Java 8 releases before finally arriving in Java 9 in September 2017. This extended development period wasn't wasted—it resulted in a carefully designed system that balances power with usability, and provides migration paths for existing code while enabling new architectural patterns.

Before modules, Java provided only packages and access modifiers for code organization. Packages group related classes, and access modifiers (public, protected, package-private, and private) control visibility at the class and member level. However, this system has inherent limitations. Any public class in any package on the classpath is accessible to all other code. There's no way to say "this package is part of my library's implementation and shouldn't be used by external code" while still allowing other packages within your library to access it. This led to widespread use of packages with names like internal or impl as conventions to signal intent, but nothing prevented code from depending on these packages anyway.

The classpath mechanism itself created problems that grew more severe as applications scaled. The classpath is a simple list of locations (directories and JAR files) where the JVM looks for classes. When multiple JARs contain classes with the same fully-qualified name, the first one found wins—a situation called "classpath hell" that leads to unpredictable behavior. There's no way to specify that your code requires a specific version of a dependency, or to have different versions of the same library available for different parts of your application. Dependencies are implicit—you only discover missing dependencies at runtime when code tries to load a class that isn't available. These limitations became increasingly problematic as applications grew larger and dependency graphs became more complex.

The Java Module System, formally specified in JSR 376 and implemented through several JEPs (JDK Enhancement Proposals), addresses these limitations through several key mechanisms:

  • Explicit dependencies: Modules declare what other modules they require, verified at compile time and startup
  • Strong encapsulation: Modules control what packages they export, hiding implementation details
  • Reliable configuration: The module system ensures all dependencies are present before the application runs
  • Scalable platform: The JDK itself is modularized, enabling custom runtime images with only needed components
  • Improved performance: The module graph enables optimizations in class loading and memory usage

The module system introduces the module-info.java file, a special source file that declares a module's name, dependencies, and exports. This file sits at the root of your module's source tree and is compiled into module-info.class, making module information available at runtime. The syntax is straightforward but powerful. A simple module declaration might look like this:

module com.example.myapp {
    requires java.sql;
    requires com.example.utils;
    exports com.example.myapp.api;
}

This declaration creates a module named com.example.myapp that depends on the java.sql module (part of the JDK) and another module called com.example.utils. It exports the com.example.myapp.api package, making it available to other modules, while keeping all other packages in the module hidden. The module system verifies at compile time that the required modules exist and that they export the packages your code uses. At runtime, the JVM ensures all required modules are present before starting the application, eliminating a whole class of runtime errors.

The transition to modules required careful attention to backward compatibility. Java 9 introduced the concept of the unnamed module, which contains all code on the classpath that isn't in a named module. This allows existing applications to run on Java 9+ without modification—classpath-based code continues to work as before. The unnamed module can read all other modules and exports all its packages, maintaining the traditional classpath behavior. Additionally, automatic modules provide a bridge for existing JAR files: when you place a regular JAR on the module path, it becomes an automatic module with a name derived from the JAR filename, exporting all its packages and requiring all other modules. This enables gradual migration—you can modularize your code while still depending on libraries that haven't been modularized yet.

The evolution continues beyond Java 9. Subsequent releases have refined the module system and added related features. Java 10 introduced improvements to module resolution and error messages. Java 11, the first long-term support release with modules, saw increased adoption as organizations began migrating from Java 8. Java 14 added helpful warnings about illegal reflective access, preparing developers for stricter encapsulation in future releases. Java 16 made strong encapsulation the default, requiring explicit flags to access internal APIs. Each release has strengthened the module system while providing tools and migration paths to help developers adapt their code. Understanding this evolution helps you appreciate both the design decisions behind the module system and the practical considerations for adopting it in your projects.

What You'll Learn in This Book

This book provides a comprehensive, practical guide to building modular Java applications. The content is organized to take you from fundamental concepts through advanced techniques, with each chapter building on previous material while remaining useful as a standalone reference. You'll learn not just the syntax and mechanics of the module system, but how to think about modularization as an architectural tool and how to apply it effectively in real-world scenarios. The emphasis throughout is on practical application—every concept is illustrated with concrete examples and actionable guidance you can apply to your own projects.

The journey begins with understanding the Java Module System itself. You'll learn what modules are, how they differ from packages, and the core concepts that underpin the module system. The module-info.java file and its various directives (requires, exports, opens, provides, uses) will become familiar tools. You'll understand the different types of modules—named, automatic, and unnamed—and when each is appropriate. This foundation is essential for everything that follows, providing the vocabulary and mental models needed to design and implement modular systems effectively.

With the basics established, you'll move into designing modular architecture. This is where modularization becomes an architectural discipline rather than just a language feature. You'll learn principles for identifying module boundaries, managing dependencies between modules, and avoiding common pitfalls like circular dependencies. The focus is on creating modules that are cohesive internally but loosely coupled to other modules—the same principles that guide good object-oriented design, applied at a higher level of abstraction. You'll see how to apply domain-driven design concepts, layered architecture patterns, and other architectural approaches using modules as the implementation mechanism.

Encapsulation receives dedicated attention because it's one of the most powerful aspects of the module system. You'll learn how to use exports and requires declarations to control visibility and dependencies. Qualified exports let you expose packages to specific modules rather than to everyone. Transitive dependencies allow you to expose dependencies that are part of your API. The chapter on encapsulation shows how these mechanisms work together to create clear boundaries and hide implementation details, with examples demonstrating both what to do and common mistakes to avoid. You'll understand how strong encapsulation improves maintainability and enables safer refactoring.

Building and compiling modular applications introduces practical considerations for your development workflow. You'll learn about the module path (distinct from the classpath), how to compile multi-module projects, and how to package modules for distribution. Integration with build tools is covered in detail—you'll see how to configure Maven and Gradle for modular projects, including multi-module builds and dependency management. IDE support is also addressed, showing how IntelliJ IDEA, Eclipse, and other development environments handle modular projects. This chapter ensures you can actually build and work with modular code, not just understand it theoretically.

Testing modular applications requires different approaches than testing traditional Java code. You'll learn strategies for testing module boundaries, creating test modules, and organizing test code in modular projects. The chapter covers how to use popular testing frameworks like JUnit 5 with modules, how to test internal implementation without breaking encapsulation, and how to structure integration tests that span multiple modules. Testing is often overlooked in discussions of the module system, but it's critical for building reliable modular applications, and this book gives it the attention it deserves.

Migration strategies address the practical reality that most developers work with existing codebases, not greenfield projects. You'll learn how to plan a migration to modules, including analyzing dependencies, identifying migration candidates, and choosing between big-bang and incremental approaches. The chapter covers handling legacy code and third-party dependencies that may not be modularized, using automatic modules as a bridge, and dealing with common migration challenges like split packages and reflection. Real-world migration is rarely straightforward, and this chapter provides the guidance needed to navigate the complexities successfully.

Advanced module features explore capabilities beyond basic module declarations. You'll learn about open modules and how they interact with reflection, service providers and service locators for building plugin architectures, module layers for dynamic configuration, and version selection during module resolution. These features enable sophisticated architectures and solve specific problems that arise in complex applications. While not every project needs these advanced capabilities, understanding them expands your toolkit and helps you recognize situations where they provide elegant solutions.

Performance and optimization demonstrate the practical benefits of modularization beyond code organization. You'll learn how modules improve startup performance, reduce memory footprint, and enable more efficient class loading. The chapter covers profiling modular applications, identifying performance bottlenecks, and optimization techniques specific to modular code. You'll see concrete measurements showing the performance impact of modularization and learn how to maximize these benefits in your applications. Performance improvements are often a compelling reason to adopt modules, and this chapter shows you how to achieve them.

Real-world modularization patterns bring everything together with practical examples and case studies. You'll see how to implement layered architectures with modules, use modules in microservices and distributed systems, build plugin systems with service providers, and apply modularization in various architectural contexts. The case studies examine real projects that have adopted modules, discussing the challenges they faced, the solutions they implemented, and the results they achieved. These examples provide templates and inspiration for your own modularization efforts, showing how the concepts and techniques from earlier chapters apply in practice.

How to Use This Book

This book is designed to serve both as a learning path and as a reference. If you're new to the Java Module System, reading sequentially from beginning to end provides a structured introduction that builds your understanding progressively. Each chapter assumes familiarity with concepts from previous chapters, so following the order ensures you have the necessary foundation. However, if you're already familiar with basic module concepts and want to dive into specific topics like testing, migration, or performance optimization, each chapter is written to stand alone as much as possible, with references to related material in other chapters when needed.

The book assumes you have intermediate Java knowledge. You should be comfortable with core Java concepts like classes, interfaces, packages, and access modifiers. Familiarity with build tools like Maven or Gradle is helpful but not required—the book explains what you need to know about build tool configuration for modular projects. You don't need prior experience with the module system; that's what this book teaches. However, if you've encountered modules in passing or struggled with module-related errors, this book will clarify the concepts and show you how to work with modules effectively.

Each chapter follows a consistent structure designed to maximize learning and retention:

  • Conceptual foundation: Core concepts are explained clearly with examples
  • Practical application: Concrete examples show how to apply concepts in real code
  • Common pitfalls: Typical mistakes and how to avoid them are highlighted
  • Best practices: Recommended approaches based on experience and community consensus
  • Actionable guidance: Specific steps you can take to implement what you've learned

Code examples throughout the book are complete and practical, not just fragments. When a module declaration is shown, you'll see the full module-info.java file, not just isolated directives. When build tool configuration is discussed, you'll see complete pom.xml or build.gradle files, not just snippets. This approach ensures you can understand examples in context and adapt them to your own projects. All examples follow modern Java conventions and best practices, so you can use them as templates for your own code. The examples progress from simple to complex, starting with minimal cases that illustrate specific concepts and building toward realistic scenarios that reflect actual application development.

Practical exercises and thought experiments appear throughout the book to reinforce learning. These aren't formal exercises with solutions in the back—instead, they're prompts to apply concepts to your own code or to think through scenarios. For example, after learning about module boundaries, you might be prompted to consider how you would modularize a system you're familiar with. After learning about encapsulation, you might consider what packages in your current project should be exported versus kept internal. These prompts help you internalize concepts by connecting them to your own experience and context.

The book includes references to official documentation, JEPs (JDK Enhancement Proposals), and other authoritative sources. These references serve two purposes: they provide evidence for claims made in the text, and they point you to additional resources for deeper exploration. The Java Module System is well-documented in official sources, and this book complements that documentation by providing explanation, context, and practical guidance that official documentation often lacks. When you need more detail on a specific aspect of the module system, the references guide you to the right resources.

As you work through the book, consider setting up a practice project where you can experiment with concepts as you learn them. Start with a simple multi-module project—perhaps a basic application with separate modules for domain logic, data access, and a simple API. As you progress through chapters, apply new concepts to this project. Add service providers when you learn about them, refactor module boundaries as you learn design principles, configure your build tool as you learn about build integration. This hands-on practice reinforces learning and gives you a safe environment to make mistakes and learn from them. By the end of the book, you'll have a working modular application that demonstrates the concepts and techniques you've learned.

Finally, remember that modularization is a means to an end, not an end in itself. The goal isn't to modularize everything possible, but to use modules where they provide value—clearer architecture, better encapsulation, improved maintainability, or enhanced performance. As you learn about the module system, keep asking yourself how these capabilities help you build better software. Some projects benefit greatly from modularization; others may not need it. This book gives you the knowledge to make informed decisions about when and how to use modules, and the skills to implement them effectively when you choose to do so. The module system is a powerful tool in your development toolkit—this book ensures you know how to use it well.

Back to contents

Notes

Introduction

  1. 1. Oracle, JEP 282: jlink: The Java Linker, OpenJDK Enhancement Proposals, 2017

This is the end of the free sample. The full book has 9 chapters.

Want this book written for you?

Tell us your goals and background, and get your own version of this book, tailored by AI to you.

User avatar
User avatar
User avatar
User avatar
User avatar
9,333 books created by readers like you
As seen on:
Product HuntRedditMediumDEV

Benefits of AI-tailored books

Read one book, not ten:
all the Java knowledge you need, gathered into a single book.
Save days of learning:
tell us your goals, choose your topics, and the whole book is built around them.
Learn effortlessly:
Java book written for your specific background and expertise.
Understand at a glance:
includes charts, tables, timelines, checklists, decision trees, and more.
Stay current:
written the day you order it, not years ago.
Trust what you read:
every book is checked for accuracy, with real research when your subject needs it.

Create your unique book in 3 steps

1. Select your focus

Select the focus of your Java book and share your background

Your Java book focus
2. Personalize your book

Specify your goals and choose sub-topics to include

3. Get your tailored book

Your book is ready in 10 minutes. Read it online, download as EPUB or PDF, or send to Kindle.

Frequently asked questions

What is TailoredRead?

TailoredRead creates complete, full-length nonfiction ebooks, written specifically for you. Tell us what you want to learn and why, and TailoredRead writes you a complete ebook on it, with charts, tables, and diagrams throughout. Read it online, on Kindle, or on any e-reader, or download it as a PDF or EPUB. Every book is written fresh the day you order it.

How is the book tailored to me?

Before anything is written, you answer a few short questions about your goals, your background, and the topics you care about. Your book is then planned around your answers: written at your level, focused on your goals, and covering the topics you chose, instead of the one-size-fits-all book a store sells everyone.

Will it read like generic AI content?

No. Your book isn't a chat transcript: it's planned chapter by chapter before a single word is written, told in a clear book voice, and explains ideas visually with charts, tables, and diagrams. After writing, every book goes through an editorial pass that polishes chapters so the book reads consistent from start to finish. Judge for yourself: browse thousands of sample books on our Explore Books page.

How accurate is the content?

Our AI first checks whether your subject needs live research. Niche and fast-moving subjects are researched before writing, and the book plan and chapters are grounded in what it finds. After writing, every book goes through a quality pass that checks its facts and consistency, and researched books list their sources in a Notes section at the back.

How long is the book?

You choose the length: Comprehensive (250-300 pages), Detailed (150-200 pages), Essential (70-100 pages), or Short (30-50 pages).

Does the book include visuals?

Yes. Every book includes charts, tables, and diagrams: timelines, checklists, step-by-step guides, decision trees, comparison tables, matrices, and bar charts. A typical book includes dozens of these visual elements.

Can I read it on Kindle? What formats can I download?

Yes. Every book can be read in our online reader or downloaded as a PDF or EPUB. The EPUB can be opened with Kindle, Apple Books, Kobo, and any e-reader. The Commercial and Author licenses also include an editable Word (DOCX) and Markdown download.

How much does it cost?

About the price of a regular ebook. The exact price depends on your book's length and complexity, from $2 for the shortest books to $35 for the most comprehensive, and it's shown when you review your book, before you commit to anything. Each book is a one-time purchase, yours to keep, no subscription. Licenses that let you share, teach, or publish your book range from $35.99 to $49.99.

Can I preview the book before purchasing?

Before you pay, you'll see what your book will cover: the title, a detailed description, the estimated length, and the complete chapter-by-chapter table of contents, so you know what you're getting before you decide. The full text itself is written after you purchase.

How long does it take to create a book?

About 10 minutes. You can start reading the opening chapters while the rest is still being written.

Personalize and buy this book

Get your personalized version, tailored by AI to your goals, background, and interests