Back to Posts

Building tightly coupled systems using Java 8 default methods

Posted in java-8, default-methods, coupling, software-development

4 min read

One of the new features of Java 8 is the concept of default methods in interfaces (1).

“Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces”.

This is excellent, you can now extend your interfaces without breaking current implementation (backwards compatibility). As great as this idea is, developers have found another usage for default methods: build tightly coupled systems that look loosely coupled.

Let’s say I’m building a couple of IoT cyborg pets to keep me company, a duck and an owl. My code would look something like this:

public class Duck {}
public class Owl {} 

Now I want to add a common fly behaviour to them, how do I achieve this? By inheritance, of course (2): I create a base abstract class, add the fly behaviour there, and make my cyborgs to extend the base class:

public abstract class FlyingCyborg {	
    public void fly() {
        //implementation goes here e.g. move wings
    }
}

public class Duck extends FlyingCyborg { }

public class Owl extends FlyingCyborg {	}

My cyborg pets can now fly. Beautiful.

It would be great if they could also run. But wait, I have a problem: I cannot add run behaviour to the FlyingCyborg class, or I would break the Single Responsibility Principle (3), and Java doesn’t allow multi-inheritance. Oh no, horror!

Default methods to the rescue!

Yes, thank you Java 8! Now I can work around all these nasty impediments (4) and make my code great again!

Easy-peasy: I create an interface RunningCyborg with one default method run, and I make my lovely pets to implement this interface. This way my cyborgs will inherit run behaviour:

public interface RunningCyborg {
    default void run() {
        //implementation goes here e.g. move legs, or wheels
    }
}

public class Duck extends FlyingCyborg implements RunningCyborg { }

public class Owl extends FlyingCyborg implements RunningCyborg { }

Voilà! My pets can now fly and run.

Now, they will have to sleep, won’t they?

public interface SleepingCyborg {
    default sleep() { 
        //implementation goes here e.g. shut systems down
    }
}

public class Duck extends FlyingCyborg implements RunningCyborg, SleepingCyborg { }

public class Owl extends FlyingCyborg implements RunningCyborg, SleepingCyborg { }

And so on, and so forth. Beautiful, isn’t it?

No, it’s not. It’s horrible! If you haven’t noticed it, this is me being ironic. This is wrong, really wrong. I have built a tightly coupled system (5). My cyborg pets are tightly coupled to the abstract class and to the interfaces. I cannot test them separately: mocking an abstract base classe is hard and how do I even mock a default method? (6). And how do I test it? My rule of thumb: if you cannot test it, don’t write it.

“Favour composition over inheritance”, in case you haven’t heard before (7).

As weird as all of this might sound, it’s happening right now. I’m currently working on a project where default methods are used this way. This leads to the testing ice-cream cone anti-pattern (8), which prevents sustainable development.


(1) Default methods
(2) No, of course not.
(3) Single Responsibility Principle
(4) SOLID principles
(5) Monolithic application
(6) There might be a way you can mock a default method, but you would at least need composition instead of inheritance
(7) Composition vs inheritance: how to choose
(8) The testing ice-cream cone anti-pattern (aka inverted pyramid of testing)

Read Next

Tennis Score for Pebble