In Java we have 4 access modifiers that can be applied to methods and
fields of a class. In descending order of visibility, they are:

public String publicModifier ="Anyone can access me";protected String protectedModifier ="Only accessible to classes in the same package as me, and my subclasses";
String defaultModifier ="Only accessible to classes in the same package as me";private String privateModifier ="Only accessible within this class";

This article is a love letter to the third in this list of modifiers, the
one-who-shall-not-be-named. It’s usually referred to the ‘default’ or
‘package-private’ access modifier.

Be aware of ingrained rules you don’t fully understand

When I first started learning Java, I read that you should never use the
default access modifier. This was because it wasn’t obvious if you had
thought about the desired visibility of your method or field, or if you were
being lazy or had just forgotten.

Use private wherever you can, protected if you need access to it in your subclasses, and public if you need it from the outside.

I didn’t question this decree at the time.

Soon enough, I started feeling uncomfortable seeing methods and fields without
modifiers, and always applied the appropriate modifier whenever I found one,
according to the rule above.

I later started using IntelliJ full time.
IntelliJ has a feature where it can infer the required access modifier by
looking at all the compiled code in its context, and see where the class member
is used. This feature is handy for seeing when something could be
private/protected, but I noticed that by default it also checks if its
visibility can be reduced to package-private. Surely that’s a feature everyone
disables. I know I did.

This layer-oriented format is so common that it even appears in Spring’s
documentation,
and 95%+ of the projects I see use this format for web applications. This
diminishes the integrity of the architecture, implements the wrong kind of
seperation of concerns, and leads to code that’s harder to maintain and split
apart. A much cleaner approach is to organise your code by feature:

This approach gives you a clearer view of your architecture as it emphasises
features over design patterns. More importantly, the coupling between
features is necessarily more obvious. Any non-Stuff related dependencies
inside any of the Stuff classes are declared in the imports. I recommend
this article if you
are interested in learning more about the perils of layer oriented
architectures.

Rethinking best practices

Allow me to diverge for a second and bring up a point I learned from the
JavaScript (no relation) community. Ever since I first started learning about
React, I’ve been plagued by this notion of rethinking best
practices and if it applies to
other best practices I’m currently following. If you squint you can see how
React is a prime example of a technology that embodies the feature-oriented
architecture in that it combines HTML and JavaScript (and often CSS as well) in
a single component. In other words, it associates your view logic (JS) with
your template (HTML/CSS) — exactly because they are conceptually related
(highly cohesive). However, React components are not necessarily related to
each other (loosely coupled).

Rethinking Java best practices

Back to the default access modifier. I believe that the default access modifier
was designed with exactly the feature-oriented package structure in mind.
Say that you have a StuffController and StuffService that looks something
like the following:

You may have noticed that I still keep public in a couple of places where it’s not strictly necessary. I firmly believe that code should express its own intent. I tend to prefer code readability over code minimalism; two concepts that are related, but different. In the case of the controller class I use public to show that this class is in fact accessed from outside the package. Similarly, the public@GetMapping annotated method is accessed from outside the package as well.

When adjusting to this new mindset of loving the default modifier, the lack of
a modifier on the findAllTheStuff() method may feel uncomfortable at first.
I used the following mantra to guide my intuition until I got comfortable with
the access modifier nudity:

By default, we want this functionality to remain within the feature.

Anything else is one of:

Explicitly made available to any class (public).

Explicitly made available to subclasses (protected).

Explicitly restricted to only this class (private).

Notice how the imports are now only classes that are unrelated to this feature.
Take advantage of this symbiosis between the default access modifier and
not having to explicitly import classes from the same package. How often do you
check the imports when you’re writing code or doing a code review? Chances are
they’re entirely ignored. I claim that imports are crucial in understanding the
coupling between units. Imports within the feature do nothing but hide
excessive coupling in a forest of boilerplate code.

Case in point: A developer skilled in the Spring
Framework might have
noticed the @Autowired annotation from the imports alone, which is redundant
in both of these classes.

In high-quality code I would expect that the quantity of methods increase as
the access modifier gets more restrictive. Perhaps the default modifier would
have been more intuitive had the absense of a modifier meant private rather
than package-private. It certainly would have shaved many a character from my
classes. This is what I believe is the design flaw behind the default access
modifier, rather than its existence in the first place. Hindsight is 20/20 I
guess.

Conclusion

In today’s world of microservices and cloud architecture a common pattern for
managing large systems is extracting sprout microservices (read more about this
topic in my article in my company’s
blog). This is a
trivial exercise when the architecture of your service is oriented around
features rather than layers.

The default access modifier in Java does have a purpose, and it’s one of
feature encapsulation. It’s such a shame that its use has become misunderstood
and frowned upon.

PS:

I started writing this article before Java 9 came out but I have to say, I’m
really excited about Java 9. It comes with a module system that adds additional
encapsulation at a package level. For example, in Java 8 and earlier you could
override the implementation of package-private methods in any third party
library by placing your class in the library’s package and inheriting. This is
no longer possible. I expect that once this new module system has gained some
traction we’ll start seeing some new patterns around organising our code. I’m
hoping developers become more conscious of their package structure. Although,
I’m not holding my breath on that one.