Kinbiko

Java: Feature-oriented package structures and the default access modifier

February 18, 2018

Advanced technical article.

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.

Prefer feature-oriented architectures over layer-oriented architectures.

I have grown to dislike this (very common) layer-oriented layout of packages:

src/main/java/com/companyname/productname/
  controller/
    StuffController.java
    ThingController.java
  converter/
    StuffConverter.java
    ThingConverter.java
  dao/
    StuffDao.java
    ThingDao.java
  model/
    Stuff.java
    Thing.java
  service/
    StuffService.java
    ThingService.java
  util/
    StuffHelper.java
    ThingHelper.java

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:

src/main/java/com/companyname/productname/
  stuff/
    StuffController.java
    StuffConverter.java
    StuffDao.java
    Stuff.java
    StuffService.java
    StuffHelper.java
  thing/
    ThingController.java
    ThingConverter.java
    ThingDao.java
    Thing.java
    ThingService.java
    ThingHelper.java

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).

diagram of conventional frontend architecture and the modern react architecture

Credit: Michele Bertoli

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:

package com.companyname.productname.controller;

import com.companyname.productname.converter.StuffConverter;
import com.companyname.productname.model.Stuff;
import com.companyname.productname.service.StuffService;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StuffController {

    private final StuffConverter converter;
    private final StuffService service;

    @Autowired
    public StuffController(final StuffService service,
                           final StuffConverter converter) {
        this.converter = converter;
        this.service = service;
    }

    @GetMapping("/names")
    public List<String> getAllNames() {
        final Set<Stuff> stuff = service.findAllTheStuff();
        return converter.extractAllNames(stuff);
    }

}

package com.companyname.productname.service;

import com.companyname.productname.dao.StuffDao;
import com.companyname.productname.model.Stuff;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class StuffService {

    private final StuffDao dao;

    @Autowired
    public StuffService(final StuffDao dao) {
        this.dao = dao;
    }

    public Set<Stuff> findAllTheStuff() {
        //Other logic...

        return dao.getAllStuff();
    }
}

If we switch to the feature-oriented package structure and use the default access modifier the way it was intended, we get terser code. Notice how all the redundant imports went away.

package com.companyname.productname.stuff;

import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class StuffController {

    private final StuffConverter converter;
    private final StuffService service;

    @Autowired
    StuffController(final StuffService service,
                    final StuffConverter converter) {
        this.converter = converter;
        this.service = service;
    }

    @GetMapping("/names")
    public List<String> getAllNames() {
        final Set<Stuff> stuff = service.findAllTheStuff();
        return converter.extractAllNames(stuff);
    }

}

package com.companyname.productname.stuff;

import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
class StuffService {

    private final StuffDao dao;

    @Autowired
    StuffService(final StuffDao dao) {
        this.dao = dao;
    }

    Set<Stuff> findAllTheStuff() {
        //Other logic...

        return dao.getAllStuff();
    }
}

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.


Roger Guldbrandsen

Written by Roger Guldbrandsen.