Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Do Not Use AllArgsConstructor In Your Public API

DZone's Guide to

Do Not Use AllArgsConstructor In Your Public API

How to avoid compatibility issues that arise from using AllArgsConstructor when you modify classes in a public API.

Free Resource

The Integration Zone is brought to you in partnership with Cloud Elements. What's below the surface of an API integration? Download The Definitive Guide to API Integrations to start building an API strategy.

Introduction

Do you think about the 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 a generated constructor in a 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 a 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 an optional field is considered a 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 Can We Solve This Problem?

After Adding Field Add Previous Constructor

If you still want to use AllArgsConstructor you have to ensure compatibility by adding a previous version of a 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 only use Data annotation, then a 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 allArgsConstructor'() {
        when:
            Person p = new Person('John', 'Smith')
            p.age = 30
        then:
            with(p) {
                firstName == 'John'
                lastName == 'Smith'
                age == 30
            }
    }
}

After adding a new field, the email test still passes.

Use Builder Annotation

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

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

After adding the email field, the 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.

Your API is not enough. Learn why (and how) leading SaaS providers are turning their products into platforms with API integration in the ebook, Build Platforms, Not Products from Cloud Elements.

Topics:
java ,api best practices ,lombok ,groovy ,compatibility

Published at DZone with permission of Dominik Przybysz. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}