21 June 2015

private int x;

public int getX() {
	return x;
}

public void setX(int x) {
	this.x = x;
}

We all have seen a lot of code like this in Java projects. So, what problems does it have?

  • Its a lot of boilerplate code.

  • Its akward to use: Calling many setters to initialize a data object costs many lines of code.

And what are its advantages?

  • It is adhereing to the Java beans standard.

    • All Java developers know it and use it.

    • Java beans can be used with many tools.

  • Interface (the getter and setter method) and implementation (the integer field itself) are decoupled.

Fighting boilerplate code

The most simple way to fight all the boilerplate code is to simply use public fields:

public int x;

But now we loose all of the advantages mentioned above.

The other way to solve this is lombok. This is a really great solution for developers of services and apps. It doesn’t work well for developers of libraries because it would force lombok on all of the users of the library. In those cases it is better to generate the boilerplate code.

Concise API

private int x;

public Data(int x) {
	this.x = x;
}

The easiest path to a concise API are constructors. Unfortunately the you have to remember the exact order of the constructor arguments. And these can be very many for big data transfer objects. This simply doesn’t work if many arguments are of the same type (e.g. strings or integers).

private int x;

public Data withX(int x) {
	this.x = x;
	return this;
}

The modern way to solve this in the Java world is a fluent API (builder pattern). The data classes have a default constructor and the data is set with so called 'withers' (named this way because the methods start with 'with'). Their main disadvantage is that they doesn’t work nicely with inheritance. To get the correct return type in all cases very ugly generics have to be used.

Decision matrix

Now lets see how the different possible solutions compare with each other.

Feature void setX Data setX Data(int x) Data withX

standard Java bean

yes

no

if added

if added

concise

no

yes

yes

yes

scales well

yes

yes

no

yes

easy to read

yes

yes

no

yes

no boilerplate code

can be lomboked/generated

can be lomboked/generated

can be lomboked/generated

can be lomboked/generated

popular

yes

no

some usages with mixed results

some usages (e.g. lombok)

In the end the withers are the clear winners if added to the setters. So this is what we do at flowdev even though it adds even more (generated) boilerplate code. The exception to this rule is that we don’t declare withers in interfaces since the correct return type would require really ugly generics. Additionally we don’t do fancy things in the withers of base classes. So these should be called after the withers of the derived classes.