Lombok @With sets inherited fields on the clone to null. How do I make it copy to work?

Viewed 492

I'm trying to set up cloning with Lombok using its @With annotation, but I ran into a problem where it sets inherited fields to null. To demonstrate, say I have the following class hierarchy:

@NoArgsConstructor
@AllArgsConstructor
@Getter
abstract class Person {
    protected String name;
    protected Integer age;
}

@NoArgsConstructor
@AllArgsConstructor
@With
@Getter
class Employee extends Person {
    protected String id;
    
    @Builder
    public Employee(String name, Integer age, String id) {
        super(name, age);
        this.id = id;
    }
}

When I try doing:

var template = Employee.builder().name("John Smith").age(20).build();
var clone = template.withId("ABC123");

both clone.name and clone.age return null. Is this construct allowed? How do I make it work?

2 Answers

I cannot reproduce using your code as it is erroneous, fix the following errors and you get your code to work:

Compilation errors:

  • You cannot pass int to String. You call .age(20) but it is defined as String age.
  • The very same for id as above.
  • The Person class should be abstract class, not class abstract.
  • The full-args constructor of Person is undefined, so Employee doesn't compile. Annotate Person with @AllArgsConstructor.
  • Due to the inheritance and non-args constructor in the parent class, you must remove both @AllArgsConstructor and @NoArgsConstructor placed on Employee.

Warnings:

  • Lombok requires for @With annotation a base default constructor in the Employee class - you must define it manually: public Employee(int id). Read the specification of the @With annotation at https://projectlombok.org/features/With.

    The @With relies on a constructor for all fields in order to do its work. If this constructor does not exist, your @With annotation will result in a compile time error message.

The code starts to work after fixing all the above. Next time, please, prepare at least a compilable snippet.

@Test
void testWith() {
    var template = Employee.builder().name("John Smith").age(20).build();
    var clone = template.withId(23);
    assertThat(clone.getId(), is(23));
}

@With doesn't seem to work with inheritance. As a workaround, you can try this code:

@NoArgsConstructor
@AllArgsConstructor
@Getter
abstract class Person {
    protected String name;

    protected Integer age;
}

@Getter
@AllArgsConstructor
class Employee extends Person {
    protected String id;

    @Builder
    public Employee(String name, Integer age, String id) {
        super(name, age);
        this.id = id;
    }

    public Employee withId(String id) {
        return this.id == id ? this : new Employee(name, age, id);
    }

}

It will work, but, unfortunately, you have to implement "withId" method yourself.

Related