113

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

krzakov
  • 3,871
  • 11
  • 37
  • 52
  • 4
    Try adding a `@NoArgsConstructor` https://projectlombok.org/api/lombok/NoArgsConstructor.html – Robert Niestroj Dec 12 '15 at 20:15
  • 1
    try adding a no args constructor .AFAIK, `@Builder` will not override your no args constructor – Ken Chan Dec 12 '15 at 21:20
  • 2
    Yeah but @Id is a required field. NoArgs doesnt gonna cut it – krzakov Dec 13 '15 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? – Roel Spilker Dec 14 '15 at 17:30

8 Answers8

123

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());
    }
}
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
Jeff
  • 3,712
  • 2
  • 22
  • 24
  • 2
    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. – wollodev Dec 29 '15 at 23:11
  • 3
    Do not use `@Data` with entities. – wst Mar 15 '17 at 23:53
  • If this is concern is related to toString, equals and hashCode, I have added links to documents regarding proper implementation. – Jeff Sep 11 '17 at 15:29
  • @wst , why would `@Data` be bad idea with entities? – srnjak Oct 30 '18 at 12:47
  • 3
    @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: http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode – wst Oct 30 '18 at 13:47
  • @wst, that's reasonable. Thanks. – srnjak Oct 30 '18 at 17:47
  • Maybe use `@EqualsAndHashCode (of = {" id "})`? – Farkhod Daniyarov Jun 14 '19 at 15:37
  • That depends on the semantics of the ID field. If it is a natural identifier/business key (ISBN) or is a database independent generated identifier (UUID) then OK. Basically it is fine if it matches the database table uniqueness constraint and can be determined and set on the object before it is persisted. – Jeff Jun 14 '19 at 16:30
  • With the above configuration, self-referencing mapping doesn't work if you use Builder. Not sure if it's a bug or not. – bluelurker Apr 24 '20 at 17:04
  • Can you give an example? – Jeff Apr 27 '20 at 16:06
  • @wollodev wrote earlier, but I wanted to be sure, is setters needed when you now introduced a Builder? – Keyhan Jun 17 '22 at 08:37
  • @wollodev if you want to do updates those must be done via setters not via `toBuilder` since that creates a new disconnected instance. – Jeff Jun 21 '22 at 20:57
105

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

John John Pichler
  • 4,427
  • 8
  • 43
  • 72
  • Note, this does not automatically create accessor methods (getters). – Jeff Sep 11 '17 at 15:33
  • @Jeff then just add `@Data` – Deniss M. May 31 '19 at 06:25
  • 5
    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. – Jeff May 31 '19 at 06:28
14

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.

Karl.S
  • 2,294
  • 1
  • 26
  • 33
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
Pavel V
  • 186
  • 1
  • 5
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;
}
raisercostin
  • 8,777
  • 5
  • 67
  • 76
4

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

Amrut Prabhu
  • 1,161
  • 11
  • 11
3

i solved this using all these annotations:

@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@NoArgsConstructor(access = AccessLevel.PACKAGE)
Akash5288
  • 1,919
  • 22
  • 16
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

bluelurker
  • 1,353
  • 3
  • 19
  • 27