What Is Modularity in Java?

A feature introduced since Java 9

Dieter Jordens
Feb 21 · 7 min read
Photo by Simon Goetz on Unsplash

In this piece, I want to tell you everything you need to know about Java modules. My intent is to help Java developers who could get asked about this as an interview question or those who are looking to get Java 11 OCP certified. If you have your own reasons for reading about modules, that’s fine too! Feel free to leave a comment if that’s the case, because I’d love to hear from you.

There will be more than just theory (and I’ve left out the more advanced stuff like jdeps), so don’t be afraid to be overwhelmed!


What is a Module?

From now on it will only be possible to use explicitly exported public classes from other modules. Developers that work without modules will use unnamed modules from now on. These unnamed modules will export all their packages. This is great for backwards compatibility, however, I’d strongly suggest using named modules as early as possible. The migration process can be time consuming, especially for developers that have little to no knowledge of the module system.


A module is a uniquely named, reusable group of related packages, resources and a module descriptor that specifies the following:

  • name
  • dependencies
  • exported packages
  • provided packages
  • used services
  • which packages are open

Name

module example {}

The example module is a perfectly valid module name, but a Java module must be given a unique name. It’s recommended to give a Java module the same as the name of the root Java package contained in the module, if possible.

A module name consists of one or more Java identifiers separated by “.” tokens. So this gives us:

module continuum.be.example {}

A Java module follows the same naming rules as Java packages. I would advise not to use underscores since the underscore character might turn into a reserved identifier in the future. Apart from that, I’d also suggest keeping every part of the module name lower case, for the sake of consistency.


Dependencies

module continuum.be.example {    requires continuum.be.accounting;}

There is also a requires static directive to indicate that a module is required at compile time, but is optional at runtime. This is known as an optional dependency. Finally you can also use the transitive keyword to ensure that other modules reading your module also read that dependency, known as implied readability.


Exported Packages

The exported packages are declared in the module descriptor. Here’s one for a simple export declaration:

module continuum.be.accounting {    exports continuum.be.accounting;}

No sub-packages of the exported package are exported. That means, that if the accounting package contained a subpackage named util then the com.continuum.accounting.util package is not exported just because com.continuum.accounting is. You also don’t have to export the parent package, if you don’t want to.


Provided Packages

  • Require the other module, in this case com.continuum.service.
  • Implement the service interface OurService with a Java class OurServiceImpl.
  • Declare the service interface implementation using the provides <interface> with <implementation class> notation.
module com.continuum.implementation {    requires com.continuum.service;    provides com.continuum.service.OurService    with com.continuum.service.OurServiceImpl}

Used Services

module com.continuum.client {    requires com.continuum.service;    uses com.continuum.service.OurService;}

The advantage of not having to declare the implementation module is that the implementation module can be exchanged without breaking the client's code. You can decide what service implementation to use when you assemble the application (e.g. by specifying the version in the pom.xml file when you’re using maven). This means that the client and the service interface module are decoupled from the service implementation module.

Now the service client module can lookup the service interface implementation at runtime, like this:

ServiceLoader<GenerationService> load = ServiceLoader.load(GenerationService.class);

Which Packages Are Open

It’s also possible to open it up for only one or more modules using the opens … to keywords. It’s strongly advised not to use the open keyword to open up an entire module, but who am I to judge what you do in your spare time — whatever floats your boat!


How About Putting This Theory Into Practice?

At first, starting with a Spring JPA application, it can be challenging to understand what’s going on in the new module system. You could end up with an application not even compiling.

This fitness application highlights the Service Loader API which is a less known alternative to dependency injection. For small applications introducing dependency injection from a well-known framework can increase the size of the application enormously, which can be really bad for programs that have to worry about the memory being used. Yes, we can do it the native way!

Photo by Clément H on Unsplash

Exercise Module

In this package we have a GenerationService interface, that allows us to generate a Schedule (e.g. running schedule, fitness schedule) for a specific fitness Level which is just a regular enum.

GenerationService.java
Schedule.java

Sports Module

The implementation classes are abstracted away by the two factories (FitnessFactory and RunningFactory). This could allow different schedules to be generated depending on different parameters (e.g. based on current fitness level).

It shows that you don’t have to put your implementation classes inside your module declaration. However, it’s certainly possible to do so.

module-info.java
FitnessFactory
FitnessGenerationServiceImpl
FitnessSchedule

Main Module

We do need to specify in our pom file both the dependency that provides the interface we’re going to use, which is the exercise module, and the dependency that provides the implementation(s), which is the sports module in this case.

module-info.java
pom.xml

If we now run the following class in the main module:

Main.java

We obtain the following two lines of output:

  • Fitness schedule from a Continuum Craft Lead with level: Advanced
  • Running schedule from a Continuum Craftsman with level: Intermediate

Not bad! The user of the provided modules can work with the code he now has available at run time or provide a GenerationService implementation himself. This makes it easy to provide some mock implementation as well.


What To Do now?

There are more sports than just running and fitness — why don’t you add a beer pong schedule?

You can find the repo with more details right here: https://github.com/djFooFoo/fitness. Feel free to experiment further with how modules work in Java.

If there’s something missing or something that could be better explained, leave a comment and I will get back to you. Thank you for reading!

Better Programming

Advice for programmers.

Thanks to Zack Shapiro

Dieter Jordens

Written by

Dieter is passionate about full stack agile software development and artificial intelligence. He is working as a Software Crafter at Continuum Consulting.

Better Programming

Advice for programmers.

More From Medium

More from Better Programming

More from Better Programming

More from Better Programming

The Zero-Dollar Infrastructure Stack

1.1K

More from Better Programming

More from Better Programming

Fun Side Projects That You Can Build Today

3.1K

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade