Before Java 9 we were all victims of having implementation details leaking out onto the Classpath, thus making them accessible to everyone. Java’s public access modifier is just too … well public! Lets take a look at how Java 9 Modules give us a substantial boost in providing strong encapsulation …
Strong Encapsulation with Java 9 Modules
Java 9 Modules provide a higher level abstraction to how we structure our applications and improve on a variety of issues ranging from the need for real strong encapsulation, reliable configuration, performance and security. Although improving in all these areas are great reasons to adopt Java 9 Modules, this article focuses solely on how you can benefit from the strong encapsulation capabilities that Java 9 Modules bring to the table.
This will not be a “what is a Java 9 Module” post however for those of you who do not know what they are, this post will still prove very useful.
You often hear about how modules will really help out when building “monolith” applications. The word “monolith” is always thrown in there, like some big scary monster (kind of funny). This begs the question …
Should I be using Java 9 Modules if I’m not building a monolith?
Well let me answer that question with a question. Should you be using packages and access modifiers other than public in your project if your not building a monolith application? Absolutely, yes!
We should all be striving in improving the structure of our code bases, encapsulating and achieving loose coupling whether developing a small, medium sized or monolith application. Java 9 Modules will take us to the next level for all cases.
Java’s Public Access Modifier
I thought I already was strongly encapsulating my Classes, what’s the problem?
Pre-Java 9 doesn’t offer a mechanism that’s flexible enough to allow us to get there all the time. Yes, we can organize code in separate packages and use package protected Classes but when it comes to truly hiding those internal implementation Classes and sharing them among different packages within our own project – we are faced with a cross roads decision.
Either move all your Classes calling the shared code to the same package where the shared code resides, even if it makes no logical sense (out goes improving your logical code base structure) or change your shared Class from package protected to public. Most developers choose the latter.
Unfortunately, by doing this we break encapsulation and now leak out implementation details that find themselves publicly available on the Classpath for the whole world to import and use in their projects. This story is a common one even for the Java platform itself and many 3rd party libraries – Once you go public you can never go back!
Before Java 9, we had no native support to permit the case described above and also restrict usage of the shared Class to everyone else (outside of your project). We package everything in Jars which do not have a native mechanism to describe dependencies between them.
This has given rise to problems such as …
You may pass the compilation phase but then the JVM might not find a requested Class at runtime on the classpath, it will throw a ClassNotFoundException. This makes ensuring a successful new deployment difficult as you have no assurance that all dependencies are reliably satisfied ahead of time.
We now rely on popular 3rd party tooling like Maven and Gradle to resolve any transitive dependencies at compile time and OSGi (for runtime) in order to fill in the gap. Even process based solutions such as Continuous Integration try to catch missing Classes during runtime execution in various environments.
You may end up with multiple copies or versions of a Class from different classpath locations in which the wrong one is taken and then it becomes a hell to figure out how to resolve the problem.
The JVM will pick up the first one it finds since its a sequential search from a flattened view of the classpath.
How Java 9 Modules Provide Strong Encapsulation
Java 9 now has a native mechanism to improve on the subject of strong encapsulation. However, there is a learning curve or change in habit on how we write and structure our projects in order to take advantage of what modules offer.
Instead of creating a single big Java application and creating multiple packages within it, you instead have to create several modules with its own unique packages to interact together in order to make up your application – think multiple separate sub-projects instead of just one. Its really like playing legos! You take all these separate modules and plug them them together to build your application. We can use Java 9’s native support to take full control on which packages we decide are strictly for internal use and which packages in our module are accessible to other modules.
This gives us the freedom to change our libraries internals without worrying about potentially breaking existing client code which relied on our public Classes – even if they were documented as not to be used or were in packages with names containing *internal*.
We All Start Out With The Best Intentions
Here is an example of how to hide our implementation details from the world in pre-java 9 …
Placing the implementations (HydroElectricityProviderImpl + NuclearElectricityProviderImpl ) in the same package as the interface ElectricityProviderIF makes total sense, especially when they are package protected. There’s even the ElectricityUtility Class which is also package protected. This properly encapsulates them from the outside world – Good job!
However let’s imagine that the project keeps growing over the years and eventually someone else (not you) decides that they need to directly re-use one of the implementations in another package along with the utility Class, what then?
That person might choose to change the implementation and utility Class to public in order to access it in their package(s). Meaning that now those Classes would now be available to everyone on the Classpath. Now the project has leaked implementation details – breaking strong encapsulation.
Yes I know no one is suppose to use the implementations directly and you javadoc’ed your heart out to tell them to use the static factory method in the Interface but guess what … no one read it. Its a problem that shows its ugly head as time goes on in the project maintenance phase – we all start out with the best intentions but the fact is pre-java 9 cannot enforce this level of strong encapsulation if the above case occurs.
Strong Encapsulation With Java 9 Modules
Lets first take a look at how we could do a little re-structuring of the projects package structure with modules in mind …
Your probably wondering why I did the following …
- Moved the implementations to a separate package than that of the Interface and also made them public
- Moved the Utility Class to a separate package and also made it public
Doesn’t making them public defeat the whole purpose of strongly encapsulating them?
Just because our Classes our marked as public, it doesn’t mean that they are accessible to other modules – in Java 9 public is not as permissive anymore. The public access modifier is only totally accessible from within the module in which it resides in.
We must now explicitly export the packages we want other modules to have access to. Not all Classes will be visible to other modules though, only the one’s that are public. This is done in a new artifact called module-info.java which must reside in the root of the module itself (source root directory – i.e src/main/java).
The module-info.java files allows you to decide which package(s) are exported (exports keyword) and which module(s) are required by your module (requires keyword). There are more keywords however those are the only two I’ll cover for this example.
With this package structure in place, we now have total control of what we can make visible to the outside world (to other modules). We only explicitly export the following two packages (see below) in the module-info.java whose module name is com.mvpjava.powergrid …
- the com.mvpjava.electricity package only contains the Interface type! We only expose the Interface to the Client and NOT any of the implementations. This also closes the door to the possibility (if implementations were in same package) of someone changing them to public in the future which would inadvertently make them accessible to other modules since everything public is exported in this package now (remember that other person I mentioned before?)
- the com.mvpjava.electricity.model which contains the model that the Clients will surely make use of
Notice that we are not explicitly exporting the packages with the interface implementations and the utility Class (for internal use) thus they are only accessible within the module they reside in – com.mvpjava.powergrid in this case. We are free to modify or even remove these internal implementation details without fear of breaking any clients using them directly.
How To Use a Java 9 Module
Suppose there is another module that wants to use the packages we exported in module com.mvpjava.powergrid shown previously. How would we write that module-info.java file?
You must specify that you require (via keyword requires) a module and not packages directly. This is because the module specified has a handle on all the exported packages it encapsulates. Now you can start importing and using only what is public from exported com.mvpjava.electricity and com.mvpjava.electricity.model packages.
Here’s an example of that …
In the client code above we are only using the Interface type. Notice that we get a “The package com.mvpjava.electricity.impl is not accessible” if we try to import it – if you scroll over the red X in your IDE. The same for import ..*internal.* where the utility class resides.
But hold on … anyone can still access my internal classes via reflection, right?
Nope, Java reflection doesn’t work any more on non-exported public Classes! This is a level of strong encapsulation we’re not seen in previous version of Java and it is a very welcomed improvement.
We now can achieve strong encapsulation with the help of Java 9 Modules however you still need to define well defined public interfaces. With Modules, you can’t accidentally rely on an implementation detail that’s public but not exported.