Use custom setter in Lombok's builder

JavaLombok

Java Problem Overview


I have a custom setter in my Lombok-based POJO:

@Data
@Builder
public class User {
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private String password = null;

    public void setPassword(String password) {
        Assert.notNull(password);
        this.password = ENCODER.encode(password);
    }

but when I use the Lombok generated builder:

User user = User.builder()
    .password(password)
    .build();

my custom setter is not invoked, and so the password is not encoded. This makes me sad.

My custom setter is, of course, invoked when I use it directly:

public void changePassword(String password, User user) {
    user.setPassword(password);
}

What can I do to have Lombok's builder use my custom setter?

Java Solutions


Solution 1 - Java

Per the documentation for @Builder: Just define enough skeleton yourself. In particular, Lombok will generate a class UserBuilder, fields mirroring the User fields, and builder methods, and you can provide any or all of this yourself.

@Builder
public class User {
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private String username;

    private String password;

    public static class UserBuilder {
        public UserBuilder password(String password) {
            this.password = ENCODER.encode(password);
            return this;
        }
    }
}

Solution 2 - Java

I've accepted chrylis's answer but for completeness, here's are some ways to minimize customization and duplication.

Custom setter and builder with static helper

A static helper can be used to shares most of the set password functionality across the custom User.UserBuilder::password method and the custom User::setPassword method:

@Data
@Builder
public class User {
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private String password = null;

    public void setPassword(String password) {
        this.password = _encodePassword(password);
    }
    
    public static class UserBuilder {
        public UserBuilder password(String password) {
            this.password = _encodePassword(password)
            return this;
        }
    }

    private static String _encodePassword(String password) {
        Assert.notNull(password);
        return ENCODER.encode(password);
    }
}

Custom setter and constructor

A custom constructor can use User::setPassword which is invoked by the Lombok generated User.UserBuilder::build():

@Data
@Builder
public class User {
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private String password = null;

    User(String password) {
        setPassword(password);
    }

    public void setPassword(String password) {
        Assert.notNull(password);
        this.password = ENCODER.encode(password);
    }
}

Custom setter and constructor with static helper

Or, a little more elegantly, with a custom constructor and a static helper method:

@Data
@Builder
public class User {
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private String password = null;

    User(String password) {
        _encodePassword(password, this);
    }

    public void setPassword(String password) {
        _encodePassword(password, this);
    }

    private static _encodePassword(String password, User user) {
        Assert.notNull(password);
        user.password = ENCODER.encode(password);
    }
}

Solution 3 - Java

You are using setPassword rather than the builder's set method.

Here is what worked for me:

import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class User {
    private String username;
    private String password;

    public static class UserBuilder {
        private String password;
        public UserBuilder password(String password ) {
            this.password ="ENCRIYP " +  password;
            return this;
        }
    }

    public static void main(String[] args) {
        System.out.println(User.builder().username("This is my username").password("Password").build().toString());

    }
}

The result was: User(username=This is my username, password=ENCRIYP Password)

Solution 4 - Java

Not really an answer to the question, but an edge case that made me to spend quite some time to find the issue.
If you have Builder with custom setter on a field with Builder.Default value, the name of the field in the generated builder class is not the same as the original field. In fact you would have two fields instead of one: password$value and password$set.
Nevertheless you can use them in the custom setter in this way:

public UserBuilder password(String password) {
  this.password$value = ENCODER.encode(password);
  this.password$set = true;
  return this;
}

The tricky part is that if you use the origianl field name, Intellij IDEA doesn't warn you about that (as if everything is fine, but of course it won't compile). Already submitted a bug report to the plugin.

Solution 5 - Java

I have come across this situation at work. Below is what I've come up with. The idea is to override Lombok's protected User(User.UserBuilder b) method

@Data
@Builder
public class User {
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();

    private String password;
    private Integer otherField;

    protected User(final User.UserBuilder<?, ?> b) {
        setPassword(b.password);
        otherField = b.otherField;
    }

    public void setPassword(String password) {
        Assert.notNull(password);
        this.password = ENCODER.encode(password);
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJan NielsenView Question on Stackoverflow
Solution 1 - Javachrylis -cautiouslyoptimistic-View Answer on Stackoverflow
Solution 2 - JavaJan NielsenView Answer on Stackoverflow
Solution 3 - JavaBojan PetkovicView Answer on Stackoverflow
Solution 4 - JavaRadView Answer on Stackoverflow
Solution 5 - JavaMarcus ChiuView Answer on Stackoverflow