Over a million developers have joined DZone.

Application Design the Java 9 Way

DZone's Guide to

Application Design the Java 9 Way

Together, with Java 8 making packages without public classes possible and Java 9's modularity, see how object-oriented app design has changed.

· Java Zone ·
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

Java 8 brought many great improvements to the language. Java 9 enhanced them further! A major paradigm shift now needs to occur. The tools that Java 8 and 9 provide for application design are immensely improved. Java 8 allows static methods on interfaces. Java 9 provides a level of organization above the package: the module. Putting these two techniques together results in better designs and stronger object-oriented guarantees.

Interfaces Rule!

It feels like interfaces passed out of favor in the Java world. There was a time when classes not implementing interfaces were considered suspicious and discussed in code review. Now, major IDEs provide class templates that automatically declare classes public and don't provide or expect an "implements" clause.

James Gosling famously observed that if he were to do it differently, he might design Java without classes:

"Rather than subclassing, just use pure interfaces. It's not so much that class inheritance is particularly bad. It just has problems."

Concrete Dependencies Used to Be Hard to Avoid

Before Java 9, any public class was visible anywhere. There was no language mechanism to prevent calling code from importing and using a public class, even if it went against the intent of the class' designers. 

I'll use an example to show what I mean. Let's say I provide a Java library for marketers. My library supports the use case of sending information about a product to potential customers who request it.

package com.scottshipp.code.product.potentialcustomer;

public interface PotentialCustomer {
    void sendInformation();

I provide two separate implementations. The first supports marketers who contact their customer via email, since that's the most popular marketing channel for businesses.

package com.scottshipp.code.product.potentialcustomer;

public class EmailablePotentialCustomer implements PotentialCustomer {

The second supports marketers who contact their customer via text message, since text messaging has both a higher read and a higher response rate than email, and customers prefer it:

package com.scottshipp.code.product.potentialcustomer;

public class TextablePotentialCustomer implements PotentialCustomer {

Obviously, the intention here would be that programmers would practice good hygiene and implement their code using the interface:

PotentialCustomer customer = new EmailablePotentialCustomer( /* . . . */ );

But, since the EmailablePotentialCustomer and TextablePotentialCustomer classes are public, it means that nothing prevents someone from forming a direct dependency on the class, ignoring the interface:

EmailablePotentialCustomer customer = new EmailablePotentialCustomer( /* . . . */ );

Anyway, you might notice from the import statements there is a dependency on the concrete class either way:

import com.scottshipp.code.product.potentialcustomer.PotentialCustomer;
import com.scottshipp.code.product.potentialcustomer.EmailablePotentialCustomer;

Java 8 Makes Packages Without Public Classes Possible

Before I get to Java 9, let me comment on a remarkable Java 8 feature that has gone largely unnoticed: the ability to include static methods in interfaces. With this feature, you can cut the public class problem off at the knees by avoiding public classes. It used to be nearly impossible to have a package without any public classes, since calling code needs to instantiate them. With Java 8, though, you can create static factory methods for an interface's implementers.

For example, I could remove the public modifier from my EmailablePotentialCustomer and TextablePotentialCustomer classes, and force client code to use static factory methods on the interface instead:

package com.scottshipp.code.product.potentialcustomer;

import java.util.Objects;

public interface PotentialCustomer {
    void giveMoreInformation();

    static PotentialCustomer createForEmailContact(String name, String emailAddress) {
        return new EmailablePotentialCustomer(name, emailAddress);

    static PotentialCustomer createForSmsContact(String name, String mobilePhoneNumber) {
        return new TextablePotentialCustomer(name, mobilePhoneNumber);

Now calling code only imports the PotentialCustomer interface — and readability is better to boot:

import com.scottshipp.code.product.potentialcustomer.PotentialCustomer;

// ...

PotentialCustomer customer = PotentialCustomer.createForEmailContact(name, email);


With this approach, the benefits of static factory methods discussed in Effective Java, Item 1 are realized. For example, static factory methods have names to aid readability and the library can choose the return type of any created objects. If it makes sense to do so, the library can also cache any created instances to improve performance.

From a maintainability perspective, there are a number of important advantages as well, the main one being that you can change or altogether remove an implementation of PotentialCustomer without forcing client code to recompile. Even within a single application, this approach prevents a common and harmful brittleness seen when a change to one class cascades into the rest of the application.

Java 9 Makes Packages Themselves Hideable

Even though Java 8 makes packages without public classes possible, it's not realistic to assume that existing software doesn't already have public classes. The approach outlined above is a possible glimpse into the future of Java applications, but a variety of factors may hinder your ability to refactor to that pattern. 

With Java 9 modules, it becomes easier to explicitly declare the intended public packages of a library and thereby hide the others. A number of excellent modularization tutorials and examples are available across the Java ecosystem. A good one is still found in the Project Jigsaw QuickStart guide.

Modularizing Our Example

For the marketing library example above, let's assume that there may still be an ugly leftover package from Java 7 days, com.scottshipp.code.product.internal. Furthermore, let's assume that it exposes public classes named EmailService and SmsService.

The package name "internal" should give away that it is not meant to be consumed by client code. Nevertheless, the fact of the matter is that without Java 9 modularization, the compiler won't notice or even care if anyone imports and uses these classes. They're public!

To implement modularization and hide the internal package, library authors simply have to provide a module-info.java file declaring the exposed packages. For this example, the module-info.java would be placed in the com.scottshipp.code.product package and look like this:

module com.scottshipp.code.product {
    requires java.base;
    exports com.scottshipp.code.product;
    exports com.scottshipp.code.product.potentialcustomer;

Because there is no line here exporting com.scottshipp.code.product.internal, it will not be visible to client code. Any code attempting to import a class from com.scottshipp.code.product.internal will receive an error:

error: package com.scottshipp.code.product.internal is not visible

And There Was Much Rejoicing!

These features make new object-oriented defensive programming techniques available to Java programmers. The result should be that Java becomes better encapsulated, more maintainable, and less brittle. Application designers and maintainers now have powerful tools at their disposable to enforce object-oriented principles at compile-time and prevent costly mistakes.

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

java ,java 9 modules ,java 8 ,package coupling ,package structure ,application design ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}