Forgotten art of Package-Private scope in Java

Tomasz 1

The most popular Java access modifier is public. It’s so popular that it’s a default in Kotlin. It seems that the Java fathers had a different plan, because if you don’t provide any modifiers, the package-private is a default one.

Why bother, you may ask?

I strongly believe that in many cases the right packaging structure can save your project, and here is why.

In my previous post I described why OOP is dead. Stepping up a level, we can say that well-organised Java code is dead and sphagethii is king.

Let’s take a look at a classic example of a Spring application. Probably everyone has seen such a structure:

Some time ago, I thought that this package structure was good enough for the most basic of applications. Now I say it’s completely useless. If you’re dealing with a complex domain and your code is organised like this, you’re lost, my friend.

The main problems you will run into sooner or later are:

1. Everything is public, which means there is no encapsulation. In the next 2 years, anyone can use any class in any place without warning. It’s a straight road to an ugly big ball of mud. Your services will become a god class and your tests will become a nightmare.

2. There is low cohesion and high coupaling between packs. You also have no control over where your classes are used. Every new feature potentially adds more coupling.

3. You can’t easily break your application into smaller pieces -> microservices, because of point 2.

4. It’s hard to maintain. In the long run, it is very difficult to work with a system without a clear separation into well-designed domains/features. The lack of proper modularisation causes significant problems with the readability of the code itself and the translation of business requirements into technical language.

The solution

The solution to all this is package by feature with a package private scope.

Here you see a single public class (open padlock) that exposes an API to the whole package. All interaction goes through this class. Calling it XyzFacade is convenient, but if you like you can use XyzService.

Within this structure, our code became a well organised modular monolith. Every feature/subsystem/component should have such a structure. Then there is high cohesion at package level and low coupling between packages.
If you decide to extract a feature into a separate microservice (or delete the feature), it’s extremely easy. Just grab the whole package.

The final thoughts

To maintain your service in the long term, I strongly recommend that you start using the package-private scope. It helps to keep high cohesion and low coupling between components. With this, we can confidently consider the journey towards microservices.

*If you are using Kotlin and you want to achieve the same results as with Java package-private scope, you need to use an internal keyword, but also add a architecture test layer. Internal is not the same as package-private!

Comment 1

Leave a Reply

Your email address will not be published. Required fields are marked *