One of the key design drivers of Java modularity was strong encapsulation. By default, a type in a module is not accessible to other modules unless it’s a public type and its containing package is exported. We can choose exactly which packages we want to expose; the rest are hidden.
(For the rest of this post, when we refer to a type, that term includes classes, interfaces, enums and annotations.)
Before Java 9, we could use the Reflection API to find the types contained in a package and all the members of a type, even the private members. Any code can access any types, so nothing was properly encapsulated.
From Java 9, modular encapsulation also applies to reflection.
If you missed the previous posts in this sequence, you can find them here:
- Java Modularity Part 1 – Introduction
- Java Modularity Part 2 – Keywords and Descriptors
- Java Modularity Part 3 – Requires and Exports
- Java Modularity Part 4 – Services
Java Modularity: Reflection Control Levels
There are three levels of runtime-only access control that can be applied to reflection in modules:
- Access to a single package in a module.
- Access to a single package by specific listed modules.
- Access to all packages in a module.
Access to a single package in a module
We use the opens
module directive to allow a specific package’s public types to be accessible to code in other modules.
All the types in the specified package, their nested public and protected types, and all of the members of all those types are accessible via reflection. This reflection access is at runtime only.
Example:
module com.incusdata { opens com.incusdata.lib; }
Access to a single package by specific listed modules
We use the opens ... to
module directive to allow a package’s public types to be accessible to code in specific modules only. This works the same way as the previous open
directive, except the reflection access is only to the listed modules. We supply a list of modules separated by commas after the to
directive:
module com.incusdata { opens com.incusdata.lib to com.acme.service, com.xyz.dao; }
Access to all packages in a module
We may want all the packages in a given module to be accessible at runtime via reflection to all other modules. In this case, we open the whole module by using the open
directive in front of the module declaration, as follows:
open module com.incusdata { // normal module directives }
This is called an open module, and it allows reflective access to all types in all its packages.
Java Modularity: Reflection Defaults
By default, a module with runtime reflective access to a package can see the package’s public types (and their nested public and protected types). However, the code in other modules can access all types in the exposed package and all members within those types, including private members via setAccessible()
, as in earlier Java versions.
Code inside a module can use reflection to access all types, and all their members, in all packages in the same module.
What’s Next?
In the next post, we’ll look at the unnamed module and automatic modules. The unnamed module is a similar concept to the default package.
I’m always interested in your opinion, so please leave a comment. Your feedback helps me write tips that help you.