124

I'm using project Lombok together with Spring Data JPA. Is there any way to connect Lombok @Builder with JPA default constructor?

Code:

@Entity 
@Builder
class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
}

As far as I know JPA needs default constructor which is overriden by @Builder annotation. Is there any workaround for that?

This code gives me error: org.hibernate.InstantiationException: No default constructor for entity: : app.domain.model.Person

4
  • 5
    Try adding a @NoArgsConstructor projectlombok.org/api/lombok/NoArgsConstructor.html Commented Dec 12, 2015 at 20:15
  • 1
    try adding a no args constructor .AFAIK, @Builder will not override your no args constructor Commented Dec 12, 2015 at 21:20
  • 2
    Yeah but @Id is a required field. NoArgs doesnt gonna cut it Commented Dec 13, 2015 at 10:50
  • 3
    I don't understand what you want. How can you have a noargs constructor that makes up values? @Id is either required or not. If it is, you need a constructor parameter, if not, you can use NoArgs. What am I missing here? Commented Dec 14, 2015 at 17:30

9 Answers 9

132

Updated

Based on the feedback and John's answer I have updated the answer to no longer use @Tolerate or @Data and instead we create accessors and mutators via @Getter and @Setter, create the default constructor via @NoArgsConstructor, and finally we create the all args constructor that the builder requires via @AllArgsConstructor.

Since you want to use the builder pattern I imagine you want to restrict visibility of the constructor and mutators methods. To achieve this we set the visibility to package private via the access attribute on the @NoArgsConstructor and @AllArgsConstructor annotations and the value attribute on the @Setterannotation.

Important

Remember to properly override toString, equals, and hashCode. See the following posts by Vlad Mihalcea for details:

package com.stackoverflow.SO34299054;

import static org.junit.Assert.*;

import java.util.Random;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.junit.Test;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@SuppressWarnings("javadoc")
public class Answer {

    @Entity
    @Builder(toBuilder = true)
    @AllArgsConstructor(access = AccessLevel.PACKAGE)
    @NoArgsConstructor(access = AccessLevel.PACKAGE)
    @Setter(value = AccessLevel.PACKAGE)
    @Getter
    public static class Person {

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;

        /*
         * IMPORTANT:
         * Set toString, equals, and hashCode as described in these
         * documents:
         * - https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/
         * - https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
         * - https://vladmihalcea.com/hibernate-facts-equals-and-hashcode/
         */
    }

    /**
     * Test person builder.
     */
    @Test
    public void testPersonBuilder() {

        final Long expectedId = new Random().nextLong();
        final Person fromBuilder = Person.builder()
            .id(expectedId)
            .build();
        assertEquals(expectedId, fromBuilder.getId());

    }

    /**
     * Test person constructor.
     */
    @Test
    public void testPersonConstructor() {

        final Long expectedId = new Random().nextLong();
        final Person fromNoArgConstructor = new Person();
        fromNoArgConstructor.setId(expectedId);
        assertEquals(expectedId, fromNoArgConstructor.getId());
    }
}

Old Version using @Tolerate and @Data:

Using @Tolerate worked to allow adding a noarg constructor.

Since you want to use the builder pattern I imagine you want to control visibility of the setter methods.

The @Data annotation makes the generated setters public, applying @Setter(value = AccessLevel.PROTECTED) to the fields makes them protected.

Remember to properly override toString, equals, and hashCode. See the following posts by Vlad Mihalcea for details:

package lombok.javac.handlers.stackoverflow;

import static org.junit.Assert.*;

import java.util.Random;

import javax.persistence.GenerationType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.Setter;
import lombok.experimental.Tolerate;

import org.junit.Test;

public class So34241718 {

    @Builder
    @Data
    public static class Person {

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Setter(value = AccessLevel.PROTECTED)
        Long id;

        @Tolerate
        Person() {}

       /* IMPORTANT:
          Override toString, equals, and hashCode as described in these 
          documents:
          - https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/
          - https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
          - https://vladmihalcea.com/hibernate-facts-equals-and-hashcode/
          */
    }

    @Test
    public void testPersonBuilder() {

        Long expectedId = new Random().nextLong();
        final Person fromBuilder = Person.builder()
            .id(expectedId)
            .build();
        assertEquals(expectedId, fromBuilder.getId());

    }

    @Test
    public void testPersonConstructor() {

        Long expectedId = new Random().nextLong();
        final Person fromNoArgConstructor = new Person();
        fromNoArgConstructor .setId(expectedId);
        assertEquals(expectedId, fromNoArgConstructor.getId());
    }
}
Sign up to request clarification or add additional context in comments.

11 Comments

I had the same question as krzakov and I solved it with your hint, using @Tolerate. Thanks for that Jeff. But is there any reason why you add the @Data annotation? Setters are not necessary in this case and @Data overwrites equal/hash/toString with the default behaviour, which can create problems.
Do not use @Data with entities.
If this is concern is related to toString, equals and hashCode, I have added links to documents regarding proper implementation.
@wst , why would @Data be bad idea with entities?
@srnjak @Data by default uses all fields for generating equals and hashCode methods, including id. Simple example - you may have the same entity representation before and after save which, from Java perspective, will be different instances (with and without an id.) This may lead to confusion and consistency issues. You may use @Data if you're overriding those methods. There is chapter of Hibernate docs about it: docs.jboss.org/hibernate/orm/5.3/userguide/html_single/…
|
114

You can also solve it explicitly with @Data @Builder @NoArgsConstructor @AllArgsConstructor combined on the class definition.

3 Comments

Note, this does not automatically create accessor methods (getters).
@Jeff then just add @Data
You don't want to use @Data as this generates equals, hashCode and toString methods, which in the case of jpa entities should be hand generated. See the details in my answer above.
15

It seems that the annotations order is important here, using the same annotations, but different orders, you can have the code working, or not.

Here is a non working example:

@AllArgsConstructor
@Builder
@Data
@Entity
@EqualsAndHashCode
@NoArgsConstructor
@RequiredArgsConstructor
@Table
@ToString
public class Person implements Serializable {
  private String name;
}

And this is a working example:

@Builder
@Data
@Entity
@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
@Table
@ToString
public class Person implements Serializable {
  private String name;
}

So be sure to have the @Builder annotation at the very top position, in my case I encountered this error because I wanted to sort annotations alphabetically.

Comments

10

If the annotations lombok.Tolerate on constructor and javax.validation.constraints.NotNull on some property are used at the same time, sonarqube will mark it as a critical error: PROPERTY is marked "javax.validation.constraints.NotNull" but is not initialized in this constructor.

If the project uses SpringData with JPA, it can be solved using org.springframework.data.annotation.PersistenceConstructor (Spring annotation, not JPA!)

Then, in combination with Lombok, annotations will be like this:

@RequiredArgsConstructor(onConstructor = @__(@PersistenceConstructor))

For Lombok builder you also need to add:

@Builder
@AllArgsConstructor

Comments

6

Using @NoArgsConstructor and @AllArgsContructor will help solve the issue of having a default constructor with @Builder.

e.g

@Entity 
@Builder
@NoArgsConstructor
@AllArgsContructor
class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
}

This is because @Builder requires all argument constructor and specifying only a default constructor will cause an issue.

Here is nore explaination: https://github.com/rzwitserloot/lombok/issues/1389#issuecomment-369404719

Comments

5

To use the following combination

  • lombok
  • JPA
    • CRUD
    • proper @EqualsAndHashCode
  • immutability - public final fields
  • no getters
  • no setters
  • changes via @Builder and @With

I used:

//Lombok & JPA
//https://stackoverflow.com/questions/34241718/lombok-builder-and-jpa-default-constructor

//Mandatory in conjunction with JPA: an equal based on fields is not desired
@lombok.EqualsAndHashCode(onlyExplicitlyIncluded = true)
//Mandatory in conjunction with JPA: force is needed to generate default values for final fields, that will be overriden by JPA
@lombok.NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
//Hides the constructor to force usage of the Builder.
@lombok.AllArgsConstructor(access = AccessLevel.PRIVATE)
@lombok.ToString
//Good to just modify some values
@lombok.With
//Mandatory in conjunction with JPA: Some suggest that the Builder should be above Entity - https://stackoverflow.com/a/52048267/99248
//Good to be used to modify all values
@lombok.Builder(toBuilder = true)
//final fields needed for imutability, the default access to public - since are final is safe 
@lombok.experimental.FieldDefaults(makeFinal = true, level = AccessLevel.PUBLIC)
//no getters and setters
@lombok.Getter(value = AccessLevel.NONE)
@lombok.Setter(value = AccessLevel.NONE)

//JPA
@javax.persistence.Entity
@javax.persistence.Table(name = "PERSON_WITH_MOTTO")
//jpa should use field access 
@javax.persistence.Access(AccessType.FIELD)
public class Person {
  @javax.persistence.Id
  @javax.persistence.GeneratedValue
  //Used also automatically as JPA
  @lombok.EqualsAndHashCode.Include
  Long id;
  String name;
  String motto;
}

Comments

4

i solved this using all these annotations:

@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@NoArgsConstructor(access = AccessLevel.PACKAGE)

Comments

1

Jeff's answer works fine however, @Builder does not support self-reference relationships yet.

Check this question for more details:

JPA @OnetoOne self reference relationship with both columns non null

Comments

1

@AllArgsConstructor required if Builder is used with @NoArgsConstructor - because builder needs @AllArgsConstructor. - Simple Fix

If you only use @Builder - it will work fine because private parameterized constructor will be created by @Builder, but if you use any XArgsConstructor, it will override the builder constructor.

So if you use, @Builder and @NoArgsConstructor or default constructor, then NoArgsConstructor will override the Builder's parameterized constructor.

That is the reason you will get error like:

required: no arguments found: java.lang.String,java.lang.String
reason: actual and formal argument lists differ in length

Because your @NoArgsConstructor was expecting no arguments, but builder is trying to use same by passing parameters.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.