Hibernate - Using @JavaType & @JavaTypeRegistration Annotations Examples

This tutorial shows you how to use the @JavaType and @JavaTypeRegistration annotations introduced in Hibernate 6.

If you ever use Hibernate, you may already know if you create an entity field with a certain type, it requires a compatible type in the database. That's because some Java classes already have default mapping to JDBC types. You can read the standard mapping in the StandardBasicTypes class.

Hibernate has a class named JavaType, which is the descriptor for the Java side of a value mapping. It's always coupled with a JdbcType, which is the descriptor for the SQL/JDBC side. For standard Java types, the mapping is already defined. Therefore, Hibernate can handle it and you don't have to define the JdbcType or JavaType to use, unless you need a custom behavior.

However, particular Java types cannot be mapped by default. If you have such fields, you can use the @JavaType annotation to tell Hibernate which JavaType to use which includes the corresponding JdbcType.

Using @JavaType Annotation

The annotation can be applied on a field, method, or annotation type. It has a required argument where you need to pass a class which defines the JavaType for the annotated field, method, or annotation type. The class must be a subtype of the BasicJavaType class.

To use the annotation, you have to import it from the org.hibernate.annotations package.

  import org.hibernate.annotations.JavaType;

Set Java Type of a Field

For example, there is an entity whose one of the fields is an Object despite the values are always a String. By default, Hibernate cannot determine the corresponding JDBC or SQL type for the Object class. You'll get an error like the following.

  org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource
  [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: [PersistenceUnit: default] Unable to build Hibernate SessionFactory;
  nested exception is org.hibernate.MappingException: Unable to determine SQL type name for column 'detail' of table 'items'

Changing the field type to String can be a solution. But if you don't want to change the type, the alternative is to add the @JavaType annotation to the field. The suitable Java type to use is StringJavaType.

  @Entity
  @Table(name = "items")
  public class Item {
  
    @Id
    @UuidGenerator
    private UUID id;
  
    @JavaType(StringJavaType.class)
    private Object detail;
  
    // other columns
  }

In another example, the field is declared as Character[]. The values are an array of characters whose elements cannot be null. Since Hibernate 6.2, there is a change in the mapping for that type that may cause an exception if you don't explicitly define how to handle that type.

  Caused by: jakarta.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory;
  nested exception is org.hibernate.MappingException: The property com.woolha.hibernate6.example.model.Item#labels uses a wrapper type Byte[]/Character[]
  which indicates an issue in your domain model.

While it's possible to change the type to char[], let's assume we cannot change it. To fix the error, add the @JavaType annotation to the field by passing a suitable descriptor class as the argument. In this case, use the CharacterArrayJavaType class.

  @Entity
  @Table(name = "items")
  public class Item {
  
    @Id
    @UuidGenerator
    private UUID id;
  
    @JavaType(CharacterArrayJavaType.class)
    private Character[] labels;
  
    // other columns
  }

Besides those two descriptor classes, there are a lot of built-in descriptors in the org.hibernate.type.descriptor.java package. You can also create a custom one if necessary.

Set Java Type of a List Instance Collection

JPA allows you to define an instance collection by using the @ElementCollection annotation. If the annotated field type is a List, there are three columns that must be in the collection table which include the foreign key to the primary table, the element value, and the element index.

If the field is annotated with the @JavaType annotation, it will be applied on the element value column. For the element index column, the annotation to use is @ListIndexJavaType.

  public class Item {
  
    @Id
    @UuidGenerator
    private UUID id;
  
    @ElementCollection
    @CollectionTable()
    @JavaType(FloatJavaType.class)
    @ListIndexJavaType(IntegerJavaType.class)
    private List<Integer> purchasedStocks;
  
    // other columns
  }

In the example above, the Java type of the element value column is set to Float. Meanwhile, the Java type of the element index column is set to Integer.

Name Java Type
item_id UUID
purchased_stocks Float
purchased_stocks_order Integer

Set Java Type of a Map Instance Collection

If the field annotated with @ElementCollection is a List, there are three columns that must be in the collection table which include the foreign key to the primary table, the element value, and the element key.

If the field is annotated with the @JavaType annotation, it will be applied on the element value column. For the element key column, the annotation to use is @MapKeyJavaType.

  public class Item {
  
    @Id
    @UuidGenerator
    private UUID id;
  
    @ElementCollection
    @JavaType(IntegerJavaType.class)
    @MapKeyJavaType(StringJavaType.class)
    private Map<Object, Object> ratings;
  
    // other columns
  }

In the example above, the Java type of the element value column is set to Integer. Meanwhile, the Java type of the element key column is set to String.

Name Java Type
item_id UUID
ratings Integer
ratings_key String

Set Java Type of a Bag Instance Collection

Another way to define an instance collection is by using a bag mapping which allows duplicate elements. There are three columns that must be in the collection table which include the foreign key to the primary table, the element value, and the bag ID.

If the field is annotated with the @JavaType annotation, it will be applied on the element value column. For the bag ID column, the annotation to use is @CollectionIdJavaType.

  public class Item {
  
    @Id
    @UuidGenerator
    private UUID id;
  
    @ElementCollection
    @CollectionId(column = @Column( name = "bag_id" ), generator = "increment")
    @JavaType(StringJavaType.class)
    @CollectionIdJavaType(IntegerJavaType.class)
    private List<Object> tags;
  
    // other columns
  }

In the example above, the Java type of the element value column is set to String. Meanwhile, the Java type of the bag ID is set to Integer.

Name Java Type
item_id UUID
tags String
bag_id Integer

Set Java Type of an Instance Collection of Other Types

If you have an instance collection whose field type is neither List nor Map and not using a bag mapping, the @JavaType annotation is used to specify the Java type of the element column.

  public class Item {
  
    @Id
    @UuidGenerator
    private UUID id;
  
    @ElementCollection
    @JavaType(StringJavaType.class)
    private Set<Object> categories;
  
    // other columns
  }

In the example above, the Java type of the element value column is set to String.

Name Java Type
item_id UUID
categories String

Using @JavaTypeRegistration Annotation

There's another annotation @JavaTypeRegistration, which is used to set the default descriptor for a Java type. You can add the annotation to a package, type, or annotation type.

For example, we want to set the default Java type of Object to use StringJavaType. It can be done by adding @JavaTypeRegistration annotation to the class. As a result, all fields whose type is Object will use StringJavaType descriptor by default. You are no longer required to add the @JavaType annotation to each field.

  @Entity
  @Table(name = "items")
  @JavaTypeRegistration(javaType = Object.class, descriptorClass = StringJavaType.class)
  public class Item {
  
    @Id
    @UuidGenerator
    private UUID id;

    private Object detail;
  }

However, if the field has a @JavaType annotation with a different type descriptor, the one defined in the field (the more specific one) will be used.

Summary

The @JavaType annotation can be used to set the Java type descriptor to use. If an instance collection field uses the annotation, it will be applied on the element value column. Hibernate also provides another annotation @JavaTypeRegistration, which can be used to set the default descriptor for a Java type.

You can also read about: