javahibernatejpaembeddablejava-record

Using Java records as JPA embeddables


I want to use Java records as embeddable objects with JPA. For example I want to wrap the ID in a record to make it typesafe:

@Entity
public class DemoEntity {

    @EmbeddedId
    private Id id = new Id(UUID.randomUUID());

    @Embeddable
    public static record Id(@Basic UUID value) implements Serializable {}
}

But If I try to persist it with Hibernate 5.4.32 I get the following error:

org.hibernate.InstantiationException: No default constructor for entity:  : com.example.demo.DemoEntity$Id
    at org.hibernate.tuple.PojoInstantiator.instantiate(PojoInstantiator.java:85) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.tuple.component.AbstractComponentTuplizer.instantiate(AbstractComponentTuplizer.java:84) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
...

So it looks like Hibernate would treat the record Id like an entity, although it is an @Embeddable.

The same happens with non-id fields and @Embedded:

@Embedded
private Thing thing = new Thing("example");

@Embeddable
public static record Thing(@Basic String value) implements Serializable {}

Is there a way to use @Embeddable records with JPA/Hibernate?


Solution

  • Java records with a single field can be used for custom ID types or any other value object with AttributeConverters.

    In the entity class the ID type is used with @Id as usual:

    @Entity
    public class DemoEntity {
    
        @Id
        private Id id = new Id(UUID.randomUUID());
    
        public static record Id(UUID value) implements Serializable {}
    }
    

    Note that the record Id doesn't have any annotation.

    The converter makes it possible to use records:

    @Converter(autoApply = true)
    public class DemoEntityIdConverter implements AttributeConverter<DemoEntity.Id, String> {
      
        @Override
        public String convertToDatabaseColumn(DemoEntity.Id id) {
            return id.value().toString();
        }
    
        @Override
        public DemoEntity.Id convertToEntityAttribute(String s) {
            return new DemoEntity.Id(UUID.fromString(s));
        }
    }
    

    Don't forget to set autoApply = true to have this converter applied automatically (without referencing it explicitly on the respective field).

    Records with more than one field could be mapped with a Hibernate UserType, but that is a bit cumbersome.