Java 9 Module System: Boundaries, Encapsulation, and requires/exports

A guide to the Java 9 Module System covering module boundaries, strong encapsulation, the requires and exports directives, and practical migration patterns.

published: reading time: 27 min read author: GeekWorkBench

Java 9 Module System: Boundaries, Encapsulation, and requires/exports

The Java 9 Module System, formally called the Java Platform Module System (JPMS), is the biggest change to how Java organizes code since packages were introduced in Java 1.0. It adds a level of grouping above packages with explicit dependencies, enforced encapsulation, and a way to describe what a module offers and what it needs from the outside world.

If you have been running Java 8 or earlier, Java 9 modules feel like crossing a border with a passport. Each module declares what it exports, what it requires, and who can access what inside. No more accidentally depending on internal sun.misc code and being surprised when it disappears in an update.

This covers how modules work, how to write a module descriptor, the practical implications for your application, and where the rough edges are. For understanding related JVM internals, see JVM Architecture Overview and the post on bytecode verification.

Introduction

The Java 9 Module System, formally called the Java Platform Module System (JPMS), introduced the first structural organization above packages since Java 1.0. It adds explicit dependency declarations (requires), strong encapsulation (exports), and a mechanism for describing service implementations (uses and provides) at the JVM level. The module system is not optional on Java 9 and later — the JVM itself is built from modules, every JAR can be a module, and understanding how modules interact is essential for debugging class loading issues, migrating codebases, and using libraries that ship with their own module descriptors. The days of accidentally depending on internal sun.misc code and being surprised when an update removes it are over: modules make dependencies and exports explicit and enforceable.

For practitioners, the module system matters most in three scenarios. First, when migrating a Java 8 application to Java 9 or later, the module system requires explicit declaration of every dependency, and missing requires directives surface as NoClassDefFoundError at startup rather than failing silently at some undefined point during runtime. Second, when working with reflection-heavy frameworks like Spring or Hibernate, understanding opens and --add-opens is necessary to diagnose InaccessibleTypeException and to understand why setAccessible behaves differently on the module path versus the classpath. Third, when building libraries or applications that need to enforce internal boundaries, modules provide JVM-enforced encapsulation that is stronger than naming conventions alone.

This post covers module anatomy and how to write a module descriptor, the semantics and practical implications of the exports, requires, uses, and provides directives, how automatic modules and unnamed modules bridge the classpath world to the module world, the most common migration pitfalls (split packages, automatic module reading restrictions, over-exporting), and how to use the Service Loader pattern within the module system. By the end, you will understand what modules actually change about how Java code is organized, loaded, and accessed at compile time and runtime.

When to Use Modules

The Java Module System is not optional once you are on Java 9 or later. Every JAR file can be a module, and the JVM itself uses modules. Here is when it matters to your decisions.

Cases where understanding the module system is essential:

  • Building any library or application on Java 9 or later
  • Migrating a large codebase from Java 8 to a newer version
  • Using libraries that have their own module descriptors (module-info.java)
  • Running on a minimal JVM image created with jlink
  • Diagnosing NoClassDefFoundError or IllegalAccessError in Java 9+ environments

Cases where modules largely stay out of your way:

  • Simple applications where all code lives in a single module or no modules at all
  • Running on Java 8 or earlier (no migration planned)
  • Using only the classpath (automatic modules bridge named JARs to the module system)

When NOT to Use

The module system adds structure and enforcement, but that overhead only pays off in specific scenarios. For many applications, it creates more problems than it solves.

For small, single-JAR applications. If your application fits in one JAR with no internal package boundaries to enforce, module-info.java is ceremony without benefit. The classpath works fine. Modules matter when you have multiple JARs with real boundaries, not when you are shipping one artifact.

When you are classpath-only and plan to stay that way. If you have no intention of using jlink, no need for jmod files, and no requirement to enforce encapsulation between your own subcomponents, the classpath is still fully supported. Java 9 did not deprecate it. Many applications run as unnamed modules indefinitely without issue.

For rapid prototyping and small team projects. Module descriptors require upfront thinking about exports, requires, and boundaries. In a prototyping phase where the architecture is still shifting, module-info.java files become refactoring work that may not pay off before the code changes again. Use the classpath for exploration, adopt modules when the architecture stabilizes.

When dealing with libraries that have split packages. If you depend on a library that splits its packages across JARs, you cannot make that library a proper module without fixing the split. Automatic modules on the classpath are a workaround, but they lose some module benefits. If fixing the library is not an option, staying classpath-only sidesteps the incompatibility.

Module Anatomy

A module is defined by a module-info.java file placed at the root of a module’s source tree. This file lives alongside the packages that make up the module.

graph TD
    A[myapp.auth<br/>module-info.java] -->|requires| B[myapp.core]
    A -->|requires| C[java.base]
    B -->|requires| C
    A -->|exports| D[com.myapp.auth.api]
    B -->|exports| E[com.myapp.core.api]
    A -.->|uses| F[com.myapp.auth.spi]
    B -.->|provides| G[com.myapp.core.spi<br/>with com.myapp.core.impl]
    F -.->|implementation| G
    C -->|exports| H[java.base<br/>packages only]

The module descriptor declares four key things. requires specifies compile-time and runtime dependencies on other modules. exports makes packages publicly accessible to other modules. uses declares that the module consumes a service. provides declares that the module implements a service implementation.

A Simple Module Descriptor

// module-info.java
module com.myapp.auth {
    // This module needs the core module at compile and runtime
    requires com.myapp.core;

    // These packages are accessible to any module that requires com.myapp.auth
    exports com.myapp.auth.api;
    exports com.myapp.auth.model;

    // This module uses the password encoder service
    uses com.myapp.auth.spi.PasswordEncoder;

    // This module provides a concrete implementation of that service
    provides com.myapp.auth.spi.PasswordEncoder
        with com.myapp.auth.internal.BCryptEncoder;
}

Without this file, the same code would be an unnamed module. Unnamed modules can read everything and export nothing, which is why the classpath mostly works. Modules change the default to deny everything and require explicit permission to both read and write.

The exports Directive

exports controls which packages are visible to other modules. Only packages named in an exports directive can be accessed from outside the module.

module com.myapp.database {
    // Public API accessible to any module
    exports com.myapp.db.api;
    exports com.myapp.db.query;

    // Qualified export - only accessible to specific modules
    exports com.myapp.db.internal to com.myapp.reporting, com.myapp.analytics;

    // Not exported - only accessible within the module
    // com.myapp.db.impl is invisible to other modules
}

Qualified exports (exports ... to ...) let you restrict visibility to specific modules. This is useful when a package contains APIs needed by a small set of known consumers but you do not want to make it broadly available.

Any code in a non-exported package is inaccessible at compile time and runtime. The JVM throws IllegalAccessError if another module tries to access it. The compiler throws an error too.

The requires Directive

requires declares a dependency on another module. The dependent module gets read access to all exported packages of the required module.

module com.myapp.web {
    // Standard dependency on the web framework
    requires spring.web;

    // Static dependency - needed at compile time but not runtime
    // Useful for annotation processors and compilers
    requires static lombok;

    // Transitively required - com.myapp.web automatically gets
    // access to what spring.web requires
    requires transitive spring.core;

    // Self-dependency is not allowed
    // requires com.myapp.web;  // COMPILE ERROR
}

The transitive modifier is important. When module A requires transitive module B, any module that requires A also automatically requires B. This propagates dependencies correctly through the module graph.

requires static is a lesser-known feature. Use it for dependencies that are only needed at compile time, such as annotation processors. The dependency is not enforced at runtime.

The Service Loader Pattern

Modules integrate with ServiceLoader through the uses and provides directives. This allows for loose coupling between a service interface and its implementation.

// In the module that defines the service interface
module com.myapp.auth {
    exports com.myapp.auth.spi;
    uses com.myapp.auth.spi.PasswordEncoder;
}

// In the module that provides the implementation
module com.myapp.auth.bcrypt {
    requires com.myapp.auth;
    provides com.myapp.auth.spi.PasswordEncoder
        with com.myapp.auth.bcrypt.BCryptEncoder;
}

// Using the service in application code
ServiceLoader<PasswordEncoder> loader = ServiceLoader.load(PasswordEncoder.class);
Optional<PasswordEncoder> encoder = loader.findFirst();

The uses directive in the service consumer module and the provides directive in the service implementation module are both required. Neither side needs to know about the other at compile time, which makes this pattern useful for plugins and optional components.

unnamed Modules and Automatic Modules

When you put a JAR on the classpath instead of the module path, it becomes an automatic module. Automatic modules export all packages and require all other automatic modules. They bridge the old classpath world to the module world.

# Running with modules
java --module-path mods:lib -m com.myapp/com.myapp.Main

# Running on the classpath (backwards compatible)
java -cp mods:lib -jar app.jar

The classpath is still fully supported. Java 9 did not break backwards compatibility. The difference is that code on the classpath runs as an unnamed module, which can read and export everything. Modules enforce stricter rules.

Named modules (those with module-info.java) cannot read automatic modules in the reverse direction. An automatic module can read a named module, but a named module cannot read an automatic module. This is one of the common migration rough edges.

Production Failure Scenarios

Failure ScenarioSymptomsRoot CauseSolution
Split package across modulesError: package X appears in both module A and module BTwo modules claim the same packageMove classes or merge modules
Missing requiresNoClassDefFoundError at runtimeDependency not declared in module-infoAdd the missing requires directive
Export to unavailable moduleIllegalAccessError or InaccessibleTypeErrorQualified export not granting accessFix the target module list
Automatic module reading restrictionLayerAutocleanup error, module init failuresNamed module depending on classpath JARMove JAR to module path
Static requires on runtime classClassNotFoundException for annotation processorStatic deps not available at runtimeUse full requires or shade the processor
Service provider not discoveredServiceLoader returns emptyProvider module missing provides directiveCheck module-info of provider

Trade-off Analysis

Understanding the costs and benefits of the module system helps you decide where to invest migration effort.

AspectModulesClasspath-only
Encapsulation enforcementStrong (JVM-enforced)None (convention only)
Dependency clarityExplicit in module-infoImplicit in JAR manifest
Startup performanceFaster with AppCDSSlower cold start
Distribution sizejlink can produce minimal imagesFull JDK required
Migration effortHigh for large codebasesNone
Service discoveryBuilt-in via ServiceLoaderBuilt-in via ServiceLoader
Reflection accessRestricted by defaultUnrestricted

Implementation Snippets

Building a Multi-Module Project

# Directory structure for a multi-module project
# myapp/
#   modules/
#     auth/
#       module-info.java
#       com/myapp/auth/*.java
#     core/
#       module-info.java
#       com/myapp/core/*.java
#   build.sh

# Compile each module to a separate output directory
mkdir -p out/auth out/core

# Compile core first
javac -d out/core \
      --module-source-path modules \
      modules/core/module-info.java \
      modules/core/com/myapp/core/api/*.java \
      modules/core/com/myapp/core/spi/*.java

# Compile auth, which depends on core
javac -d out/auth \
      --module-source-path modules \
      --module-path out/core \
      modules/auth/module-info.java \
      modules/auth/com/myapp/auth/api/*.java \
      modules/auth/com/myapp/auth/internal/*.java

# Create modular JARs
jar --create --file=lib/auth.jar \
    --main-class=com.myapp.auth.AuthService \
    -C out/auth .

jar --create --file=lib/core.jar -C out/core .

# Run with the module path
java --module-path lib -m com.myapp.auth

Migrating a Classpath Application

When migrating a classpath application to modules, start by understanding your dependency graph.

// Step 1: Create module-info.java for your app
module com.myapp {
    // Declare what you need from the JDK
    requires java.sql;
    requires java.logging;

    // Declare what you need from third-party libraries
    // These must have their own module-info or be on classpath
    requires com.google.guava;

    // Export your public API
    exports com.myapp.api;
    exports com.myapp.service;
}

// Step 2: Handle internal packages
// Any package not exported is automatically internal
// Move classes that should be internal to non-exported packages

// Step 3: Handle reflection
// If you use reflection on private members, you may need opens
opens com.myapp.internal to com.google.guava;
opens com.myapp.internal to com.myapp.testing;
# After building your modular JARs
# Create a minimal runtime image with just the modules you need

jlink --output myapp-runtime \
      --module-path $JAVA_HOME/jmods \
      --add-modules com.myapp.auth,com.myapp.core,java.base \
      --launcher start=com.myapp.auth \
      --vm=server

# The resulting runtime only includes:
# - The JDK modules your app actually uses
# - Your application modules
# - A custom launcher script

./myapp-runtime/bin/start

Observability Checklist

When running module-aware applications in production, here is what to watch.

  • Monitor for IllegalAccessError and InaccessibleTypeError at startup and runtime
  • Log module resolution failures with --add-modules tracing
  • Track NoClassDefFoundError patterns that suggest missing requires
  • Verify that third-party libraries are module-aware before upgrading to Java 9+
  • Test with --illegal-access=deny to surface hidden access problems early
  • Audit uses of opens and add-opens for security and stability implications
  • Profile class loading time improvements after modularizing with AppCDS
  • Monitor for Layer-related errors during dynamic module loading

Security Notes

The module system changes how reflection works. By default, modules restrict reflective access to unexported members. If you need to access private fields or methods via reflection, you must explicitly open the package or module.

// Before Java 9, this worked on the classpath:
// Field field = MyClass.class.getDeclaredField("internalState");
// field.setAccessible(true);

// In a module, this throws InaccessibleTypeException
// unless the module opens the package:

module com.myapp {
    opens com.myapp.internal to com.myapp.testing;
    opens com.myapp.internal to com.myapp.serialization;
}

// Or open to ALL modules at once:
module com.myapp {
    opens com.myapp.internal;  // Opens to reflection but not to compile-time access
}

The --add-opens JVM flag opens packages at runtime without modifying source code. This is how frameworks like Spring and Hibernate were able to support Java 9 without breaking existing behavior. The security implication is that anything with --add-opens can access private members at runtime, so be careful where you use it.

Common Pitfalls / Anti-Patterns

Here are the most common mistakes when working with the Java Module System.

Split packages. If two JARs contain the same package name, they cannot both be modules in the same module graph. This is one of the most common migration errors. The solution is to merge the packages or keep one JAR on the classpath as an automatic module.

Assuming automatic modules work both ways. A named module can read an automatic module (because automatic modules export everything), but an automatic module cannot read a named module. This asymmetry causes failures when you put a JAR on the classpath and expect it to access a modular JAR’s internal packages.

Over-exporting. Exporting every package is tempting but defeats the purpose of encapsulation. Start by exporting nothing, then add exports only when another module genuinely needs access.

Using add-opens in production. The --add-opens JVM flag is a compatibility escape hatch, not a long-term solution. It bypasses encapsulation and can create security risks. Use it for migration and testing, then refactor to use proper module boundaries.

Forgetting that module names matter. Module names must be unique in the module graph. They do not have to match package names or JAR names. Use reverse-domain naming like com.myapp.auth for consistency.

Quick Recap Checklist

Use this checklist when migrating to or working with the Java Module System.

  • Audit your JARs for split packages before migration
  • Create module-info.java for your application module
  • Declare all JDK and third-party dependencies with requires
  • Export only the packages that form your public API
  • Move internal classes to non-exported packages
  • Use qualified exports for packages needed by specific consumers
  • Test with --illegal-access=deny before deploying
  • Replace illegal-access-flag workarounds with proper module declarations
  • Consider jlink for smaller distribution footprints
  • Use ServiceLoader with uses/provides for plugin architectures

Interview Questions

1. What is the difference between requires and requires transitive?

requires declares that your module depends on another module, giving you read access to that module's exported packages. requires transitive does the same but also propagates the dependency to any module that requires your module. If module A requires transitive B, and module C requires A, then C automatically reads B as if it had declared requires B itself. This is how you propagate dependencies correctly through a module graph without forcing every downstream consumer to declare them explicitly.

2. Why did Java introduce the module system and what problem does it solve?

The module system solves several problems. First, it provides strong encapsulation at the JVM level, not just by convention. Internal APIs like sun.misc were accessible on the classpath and applications depended on them, creating fragile code that broke on every JDK update. Second, it makes dependencies explicit and checkable at compile time rather than failing at runtime with NoClassDefFoundError. Third, it enables the creation of minimal runtime images with jlink that contain only the modules an application actually needs. Fourth, it clarifies the structure of large codebases where packages were the only organizational unit.

3. What is an automatic module and how does it interact with named modules?

An automatic module is a JAR placed on the classpath rather than the module path. The module system derives a module name from the JAR filename and treats it as a module that exports all its packages and requires all other automatic modules. An automatic module can read any named module because named modules are open for reading. However, a named module cannot read an automatic module because automatic modules have no module-info and therefore cannot be depended on by named modules. This one-way restriction is the most common source of migration errors.

4. What does the opens directive do in a module descriptor?

The opens directive makes a package accessible for deep reflection at runtime without making it accessible at compile time. This is how frameworks like Spring and Hibernate worked with Java 9. A module can declare opens package or opens package to specific-module to grant reflective access to private members. Without opens, attempting to setAccessible(true) on a field in that package throws InaccessibleTypeException at runtime. The opens directive respects encapsulation at compile time while allowing runtime reflection that frameworks need.

5. What is a split package problem and how do you resolve it?

A split package occurs when two or more JARs contain classes in the same package name. On the classpath this works fine. As modules, it fails because each package must belong to exactly one module. The module system rejects the configuration. Resolving it requires either merging all classes from that package into a single JAR, moving classes to different packages to avoid the split, or keeping the offending JARs on the classpath as automatic modules (which can see split packages but lose other module benefits). The first option is the cleanest long-term solution.

6. How does the module system affect the class loading order and what problems can this cause?

Module loading follows a specific order: first the java.base module is loaded, then all other modules are loaded based on their requires directives in topological order. A ClassLoader that loads a module must have all its requires dependencies visible. This means if module A requires B, the ClassLoader loading A must also be able to load B. If you have a custom ClassLoader and it tries to load a module without having access to its dependencies, you get NoClassDefFoundError. This is different from the classpath where all classes are visible to all ClassLoaders.

7. What is the difference between a named module, an automatic module, and an unnamed module?

A named module has a module-info.java file and is placed on the module path. It exports only what it declares and requires only what it declares. An automatic module is a JAR on the classpath that is treated as a module. The module name is derived from the JAR filename. Automatic modules export all packages and require all other automatic modules. An unnamed module is code on the classpath without a module-info.java and without being part of an automatic module. Unnamed modules can read everything and export nothing, which is why the classpath mostly works.

8. What does the exports...to directive do and when would you use a qualified export?

exports without qualification makes a package accessible to all modules. exports...to (qualified export) makes a package accessible only to the listed modules. You use qualified exports when a package contains APIs needed by specific known consumers but you do not want to make it broadly available. For example, internal testing utilities might be exported only to a testing module. The compiler and JVM enforce this: if a module not in the to-list tries to import classes from a qualified-exported package, it gets an IllegalAccessError at both compile time and runtime.

9. How does the provides...with directive work for the ServiceLoader pattern and what are the requirements?

provides declares that a module implements a service interface, and with specifies the concrete implementation class. For ServiceLoader to discover the provider, both the service consumer module (with uses) and the service provider module (with provides) must be on the module path. The provider module must declare provides ServiceInterface with ImplementationClass. The consumer module must declare uses ServiceInterface. At runtime, ServiceLoader.load(ServiceInterface.class) iterates through the module layers to find modules that declare provides ServiceInterface.

10. What is the --illegal-access option and why was it introduced in Java 9?

The --illegal-access option controls how the JVM handles attempts to access internal JDK APIs that are not exported. In Java 9, most internal JDK APIs were encapsulated but a compatibility mode allowed access with warnings. The option values are: permit (default in Java 9-15) allows access with warnings, opens allows reflective access with warnings, and deny (default in Java 16+) denies access entirely. The option was introduced to give developers time to find and fix their uses of internal APIs before they were fully sealed. Using deny in development surfaces all hidden access problems immediately.

11. How does the module system interact with reflection and what happens when you call setAccessible on a private member?

On the classpath, setAccessible(true) works on any field or method regardless of visibility. In a module, attempting to access an unexported package's members throws InaccessibleTypeException at runtime. Attempting to access an exported but private member requires the module to open the package. The opens directive or --add-opens JVM flag enables reflective access without compile-time access. Without opens, setAccessible fails even if the member is technically accessible through normal reflection rules because module encapsulation is enforced before accessibility checks.

12. What is the difference between requires and requires static in module-info.java?

requires declares a dependency that must be present at both compile time and runtime. If the required module is not available, the JVM refuses to start or the compiler fails. requires static (called requires transitive in the declaration but the modifier is static) declares a dependency that is only required at compile time. The module must be present for compilation but is not required at runtime. This is useful for annotation processors and compilers that need dependencies during compilation but do not need them when the application runs.

13. How do you handle a dependency that is on the classpath but your application is on the module path?

When a named module depends on an automatic module (classpath JAR treated as module), it works in one direction only: the named module can read the automatic module. The automatic module cannot read a named module. If you have a modular application that depends on a classpath-only library, you need to either: place that library's JAR on the module path and create a module-info.java for it, keep the modular parts and the library in separate module paths with the library on the classpath as an automatic module (but then modular code cannot access the library's internal packages), or use --add-opens to allow reflection into the library's packages.

14. What happens during module resolution when a required module cannot be found?

When the JVM or compiler cannot find a required module, it throws a LayerInstantiationException or NoClassDefFoundError during initialization. The error occurs very early in JVM startup, before any application code runs. The error message includes the name of the missing module. Common causes include: a dependency JAR is missing from the module path, the module name in requires does not match the actual module name in the dependency's module-info, or a transitive dependency is missing. Use --module-path to specify where modules are located and --add-modules to include initial modules.

15. What is the jdeps tool and how does it help with module migration?

jdeps is a command-line tool that analyzes class and JAR files for dependencies. It can generate module-info.java templates based on the JAR's manifest or class contents. It identifies internal API usage, suggests replacements from the Java Platform Module System, and shows which modules are required. For migration, run jdeps --generate-module-info on your classpath JARs to generate module descriptors, then review and refine them. jdeps can also identify uses of --add-opens and suggest proper module declarations to replace them.

16. How does the module system affect JARs that are loaded from a custom ClassLoader vs the system ClassLoader?

Modules loaded by the system ClassLoader (the application class loader) participate in the module system normally. Modules loaded by custom ClassLoaders create their own module layers within the JVM. Each ClassLoader can define modules, and modules from different ClassLoaders are isolated from each other. A module defined by one ClassLoader cannot read modules from another ClassLoader unless there is an explicit module layer relationship. This isolation affects ServiceLoader discovery, which searches within module layers.

17. What is the effect of using open modules vs regular modules on reflective access?

A regular module only allows reflective access to public members of exported packages. An open module (declared with the open modifier) allows deep reflection on all packages, not just exported ones. An open module is useful when you want to allow frameworks like Spring or Hibernate to access internal state without making those internals publicly accessible. However, open modules still do not allow compile-time access to internal packages—only reflective access at runtime is granted. opens is more targeted (per-package) while open is for the entire module.

18. How do you debug module resolution failures in a complex multi-module application?

Use the --module-path option with a colon-separated list to ensure all modules are found. Use --add-modules to specify the initial module to resolve. Use -Xlog:module=info to see detailed module resolution logging. If a split package error occurs, use --module-path to list all modules and identify which JARs contain conflicting packages. The jdeps tool can show you the full dependency graph before you try to run. For cyclic dependencies, the JVM detects them during resolution and reports LayerInstantiationException with a cycle description.

19. Can a module export the same package to multiple target modules with different visibility rules?

No, a package can only be exported once with specific visibility. You cannot export a package to module A and export it differently (e.g., more restricted) to module B. Either the package is exported to all modules (exports package), exported only to specific modules (exports package to moduleA, moduleB), or not exported at all. The export declaration applies to all modules that require the exporting module. To have different visibility for different consumers, you would need to restructure the packages, for example by having an API package and an internal package.

20. What is the relationship between the module system and the JVM's concept of a Layer?

A Layer represents a group of modules loaded by the same ClassLoader or that have a defined relationship. The JVM creates a Layer for each module path or ClassLoader configuration. The system Layer is the base layer containing java.base and other JDK modules. When you create a custom ClassLoader that defines modules, you can create a new Layer above the system Layer. Layers can have parent Layers, and module resolution traverses parent Layers to find dependencies. Layers are hierarchical and allow multiple module configurations to coexist in the same JVM.

Further Reading

Conclusion

The Java 9 Module System adds a layer of structure that was missing from Java for two decades. Used well, it gives you explicit dependencies, enforced encapsulation, and a cleaner architecture. Used poorly, it creates migration headaches and confusing error messages. The key insight is that modules are about declaring intent, not just satisfying the compiler.

Start with --illegal-access=deny in your development environment. It surfaces the places where your code or your dependencies rely on accessing internal APIs. From there, the migration path becomes clearer. Do not rush to export everything. Keep encapsulation strong by exporting only what you genuinely need to expose.

Category

Related Posts

JVM Bytecode Verification: Type Checking and Stack Map Frames

A technical deep dive into the JVM bytecode verifier, covering type checking, stack map frames, the four verification stages, and what happens when verification fails.

#java #jvm #bytecode

CDS and AppCDS: Class Data Sharing for Faster JVM Startup

A guide to Class Data Sharing in the JVM, covering how CDS and AppCDS work, how to create shared archives, and how they reduce startup time and memory footprint.

#java #jvm #cds

JVM in Containers: Cgroup Memory Limits and Heap Sizing

A guide to how the JVM detects container memory limits, configures heap accordingly, and avoids pitfalls when running Java in Docker and Kubernetes.

#java #jvm #containers