Loading...

Avoid Inconsistent Builder With Lomboks @Builder

Clean Code
May 14, 2020
3 minutes to read
Share this post:

In a project I worked on I saw that nearly every entity and value object was created with Lomboks @Builder . Their reason is that it makes it easier to construct these objects - especially for tests. But it comes with a cost. The problems that these builders create can’t be detected by the compiler and are especially dangerous in every CI environment.

Let’s look at an example. This is our object that uses the builder for construction:

@Entity
@Builder
public class UserEntity {
	@Id
	private long id;
	
	private String name;
	
	@LastModifiedDate
	private Date lastModification;
	
	private DeviceData deviceData;

}

The @Builder annotation generates an api that can be used like this:

UserEntity.builder()
	.id(UUID.randomUUID())
	.name("Marcus Held")
	.deviceData(new DeviceData())
	.lastModification(new Date())
	.build();

So, let’s have a look in which pitfalls we can fall.

Pitfall #1 Adding Fields

Imagine we add a field to the entity like:

@Entity
@Builder
public class UserEntity {
	@Id
	private long id;
	
	private String name;
	
	@LastModifiedDate
	private Date lastModification;
	
	private DeviceData deviceData;
	
	private Date registrationDate;

}

As soon as we introduce this the compiler… - does nothing. Of course not, because we don’t construct this entity by a constructor, we rely on the generated builder and the API is not forcing us to fill in the registrationDate. The developer needs to make sure to look for all creations in the code and check if the property needs to be filled or not. I discussed a possible solution in Distinguish Between Optional and Mandatory Parameters in the Builder Pattern .

Pitfall #2 Managed Properties

You might have noticed that our example uses a couple of different annotations. For example @LastModifiedDate private Date lastModification;. The @LastModifiedDate is a Spring Data annotation which fills in the date automatically. So actually we don’t want to have this in our builder. With the Builder annotation we can’t exclude this field, and even when there would be an option it wouldn’t be compile safe.

note “Tip” If you need to modify this field in a test, then consider mocking the class and test against the API instead of the property itself.

Pitfall #3 Invariants

According to the definitions of Domain Driven Design by Eric Evans the builder is a factory and its purpose is to ensure the invariant of the objects it constructs. Our builder can’t do this. For example, how should the builder make sure that the same id is not used twice? Or that the name is not breaking any bad word filter? The builder can’t do this without domain knowledge.

Conclusion

My takeaway from these thoughts is that I don’t use the @Builder annotation anymore, because its pitfalls are too severe in my opinion. What I especially dislike is that these issues are hard to find, because all of them are not detectable by the compiler and can easily slip through in a CI environment.

Have you heard of Marcus' Backend Newsletter?

New ideas. Every week!
Top