Do not use AllArgsConstructor in your public API

Introduction

Do you think about compatibility of your public API when you modify classes from it? It is especially easy to miss out that something incompatibly changed when you are using Lombok. If you use AllArgsConstructor annotation it will cause many problems.

What is the problem?

Let’s define simple class with AllArgsConstructor:

@Data
@AllArgsConstructor
public class Person {
    private final String firstName;
    private final String lastName;
    private Integer age;
}

 

Now we can use generated constructor in spock test:

def 'use generated allArgsConstructor'() {
    when:
        Person p = new Person('John', 'Smith', 30)
    then:
        with(p) {
            firstName == 'John'
            lastName == 'Smith'
            age == 30
        }
}

And the test is green.

Let’s add new optional field to our Person class – email:

@Data
@AllArgsConstructor
public class Person {
    private final String firstName;
    private final String lastName;
    private Integer age;
    private String email;
}

Adding optional field is considered compatible change. But our test fails…

groovy.lang.GroovyRuntimeException: Could not find matching constructor for: com.github.alien11689.allargsconstructor.Person(java.lang.String, java.lang.String, java.lang.Integer)

How to solve this problem?

After adding field add previous constructor

If you still want to use AllArgsConstructor you have to ensure compatibility by adding previous version of constructor on your own:

@Data
@AllArgsConstructor
public class Person {
    private final String firstName;
    private final String lastName;
    private Integer age;
    private String email;

    public Person(String firstName, String lastName, Integer age) {
        this(firstName, lastName, age, null);
    }
}

And now our test again passes.

Annotation lombok.Data is enough

If you use only Data annotation, then constructor, with only mandatory (final) fields, will be generated. It is because Data implies RequiredArgsConstructor:

@Data
public class Person {
    private final String firstName;
    private final String lastName;
    private Integer age;
}
class PersonTest extends Specification {
    def 'use generated requiredFieldConstructor'() {
        when:
            Person p = new Person('John', 'Smith')
            p.age = 30
        then:
            with(p) {
                firstName == 'John'
                lastName == 'Smith'
                age == 30
            }
    }
}

After adding new field email test still passes.

Use Builder annotation

Annotation Builder generates for us PersonBuilder class which helps us create new Person:

@Data
@Builder
public class Person {
    private final String firstName;
    private final String lastName;
    private Integer age;
}
class PersonTest extends Specification {
    def 'use builder'() {
        when: Person p = Person.builder()
            .firstName('John')
            .lastName('Smith')
            .age(30).build()
        then: with(p) {
            firstName == 'John'
            lastName == 'Smith'
            age == 30
        }
    }
}

After adding email field test still passes.

Conclusion

If you use AllArgsConstructor you have to be sure what are you doing and know issues related to its compatibility. In my opinion the best option is not to use this annotation at all and instead stay with Data or Builder annotation.

Sources are available here.

You May Also Like

Mock Retrofit using Dagger and Mockito

Retrofit is one of the most popular REST client for Android, if you never use it, it is high time to start. There are a lot of articles and tutorial talking about Retrofit. I just would like to show how to mock a REST server during develop of app and i...Retrofit is one of the most popular REST client for Android, if you never use it, it is high time to start. There are a lot of articles and tutorial talking about Retrofit. I just would like to show how to mock a REST server during develop of app and i...

Visualizing GIS data in JavaFX 2.0 beta using GeoTools

Geographic data mostly comprises of polygon coordinates sets along with attributes, like country or city name, etc. This is quite easy to visualize in JavaFX, which supports rendering for SVG paths. In the article, I show how to read such GIS data from...Geographic data mostly comprises of polygon coordinates sets along with attributes, like country or city name, etc. This is quite easy to visualize in JavaFX, which supports rendering for SVG paths. In the article, I show how to read such GIS data from...