Vision Statement
Immutable POJOs are key to bug-free programs. It is not straight forward to write immutable POJOs in Java. Builder pattern comes handy to solve this problem by separating getters and setters. The problem with the Builder pattern in java is the need to duplicate POJO properties. The purpose of this solution is to minimize the downside of the Builder pattern in java by separating the state into a separate class.
Use Case
Suppose we have a User
POJO like this:
public class User { private final String lastName; private final String firstName; private User(Builder builder) { this.lastName = builder.lastName; this.firstName = builder.firstName; } public String getLastName() { return this.lastName; } public String getFirstName() { return this.firstName; } public static class Builder { private String lastName; private String firstName; public Builder setLastName(String lastName) { this.lastName = lastName; return this; } public Builder setFirstName(String firstName) { this.firstName = firstName; return this; } public User build() { return new User(this); } } public static Builder builder() { return new Builder(); } }
The problem with the above solution is that lastName
and firstName
attributes need to be repeated for both builder and POJO itself.
Solution
To address this issue we can try to separate attributes into a separate State
class.
public class User { private static class State { private String lastName; private String firstName; } private final State state; private User(Builder builder) { this.state = builder.state; } public String getLastName() { return this.state.lastName; } public String getFirstName() { return this.state.firstName; } public static class Builder { private final State state = new State(); public Builder setLastName(String lastName) { this.state.lastName = lastName; return this; } public Builder setFirstName(String firstName) { this.state.firstName = firstName; return this; } public User build() { return new User(this); } public static Builder from(User user) { return User.builder() .setLastName(user.getLastName()) .setFirstName(user.getFirstName()); } } public static Builder builder() { return new Builder(); } }
Bonus for IntelliJ Users
IntelliJ’s generate Getter/Setter feature cannot be used effectively when attributes of the POJO are factored out into a separate State. Luckily, IntelliJ provides the ability to customize generators. Here is an example of a possible custom Getter/Setter generator scripts.
Getter
#if($field.modifierStatic) static ## #end $field.type ## #set($name = $StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field, $project)))) #if ($field.boolean && $field.primitive) is## #else get## #end ${name}() { return this.state.$field.name; }
Setter
#set($paramName = $helper.getParamName($field, $project)) public ## #if($field.modifierStatic) static void ## #else Builder ## #end set$StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field, $project)))($field.type $paramName) { #if ($field.name == $paramName) #if (!$field.modifierStatic) this.state.## #else $this.state.## #end #end $field.name = $paramName; #if(!$field.modifierStatic) return this; #end }
Navigate to the State class and generate Getters/Setters. Then copy getters to the POJO and setters to the builder. A small extra step but save a lot of boilerplate code.