Hibernate - Using @JdbcTypeCode Annotation Examples

This tutorial explains how to use the @JdbcTypeCode annotation introduced in Hibernate 6.

With JPA, you can define an entity class which represents a database entity or table. It can have some fields or columns. Hibernate already has a default mapping between Java field types and JDBC types. However, sometimes it's necessary to map a specific field to use a different JDBC type. Hibernate 6 brought a new annotation called @JdbcTypeCode, which makes it easier to do that. Below I'm going to give you some usage examples of the annotation.

Using @JdbcTypeCode Annotation

Hibernate has a class named JdbcType, which defines a descriptor for the SQL/JDBC side of a value mapping that always has a corresponding JavaType value. It defines how values are read from and written to JDBC. For some Java types, such as Integer, String, and Boolean, Hibernate already has a default mapping which can be seen in the StandardBasicTypes class.

In some cases, you may need to explicitly define the JDBC type to use for a field. For example, if you want to use a different one from the default mapping. Another case is when Hibernate cannot determine the JDBC type of a Java type.

The usage of the annotation is quite simple. Just annotate a field, a method, or an annotation interface with it. It has a required argument whose type is an integer that represents the JDBC type code to be used.

The value of a JDBC type code is an integer. Each type has a unique code value. Fortunately, you don't have to remember or search for the code for the type that you want to use. You can use the constants defined in the java.sql.Types class. With Hibernate, you can also use the org.hibernate.type.SqlTypes class, which is the extension of the java.sql.Types class.

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

  import org.hibernate.annotations.JdbcTypeCode;

Set JDBC Type of a Field

As I've written above, Hibernate has a default mapping between JDBC types and Java types. For example, if the field's Java type is Integer, the default JDBC type is INTEGER. As a result, the database column type must be compatible with the JDBC type (INTEGER) if the field type is Integer.

Let's say we want to define a field with Integer as the type because the value cannot have any decimal point. The database column uses a FLOAT type and it cannot be changed for some reasons. If the JDBC type and the database column mismatches, you may get an exception like the following when starting the application (assuming the DDL validation is on).

  org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: wrong column type encountered in column [price] in table [products]; found [float8 (Types#DOUBLE)], but expecting [integer (Types#INTEGER)]

The solution is quite simple, just annotate the field with the @JdbcTypeCode annotation by passing the type code for FLOAT.

  @Entity
  @Table(name = "products")
  public class Product {

    @Id
    @UuidGenerator
    private UUID id;
  
    @JdbcTypeCode(SqlTypes.FLOAT)
    private int price;

    // other columns
  }

Since an integer value can be converted to a double value, you should get the expected behavior when persisting or fetching a row.

Name Type
product_id UUID
price FLOAT

If the conversion between the Java type and JDBC type cannot be performed, you may get a HibernateException. For example, the field type is Integer but the JDBC type is BOOLEAN. The application can be started successfully by annotating the field with @JdbcTypeCode(SqlTypes.BOOLEAN). However, when fetching or persisting a row, Hibernate will throw an exception.

  org.hibernate.HibernateException: Unknown unwrap conversion requested: java.lang.Integer to java.lang.Boolean : `org.hibernate.type.descriptor.java.IntegerJavaType` (java.lang.Integer)

  org.hibernate.HibernateException: Unknown wrap conversion requested: java.lang.Boolean to java.lang.Integer : `org.hibernate.type.descriptor.java.IntegerJavaType` (java.lang.Integer)

In that case, you may need to use a custom converter or a custom type.

Set JDBC Type of a JSON Field

Another usage is for declaring a field whose type in the database is JSON. Before this annotation, you have to define the JSON type using a @TypeDef annotation by providing a class that handles the mapping. You can either create your own class or use a library such as hibernate-types-52. Then, you need to annotate the field using @Type annotation.

For example, in a Spring application with a PostgreSQL database, we want to define a field whose database column type is JSONB. Below is the code where the class is annotated with @TypeDef and the field is annotated with @Type.

  @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
  @Entity
  @Table(name = "products")
  public class Product {

    @Id
    @UuidGenerator
    private UUID id;
  
    @Type(type = "jsonb")
    private Map<String, Object> details;

    // other columns
  }

With the @JdbcTypeCode annotation, it becomes much simpler. You don't need to create your own class or use any library anymore, unless you need a custom implementation. Just add the @JdbcTypeCode annotation on the field by passing the type code for JSON as the value.

  @Entity
  @Table(name = "products")
  public class Product {

    @Id
    @UuidGenerator
    private UUID id;
  
    @JdbcTypeCode(SqlTypes.JSON)
    private Map<String, Object> details;

    // other columns
  }

Below are the column types of the entity above.

Name Type
product_id UUID
details JSON

Set JDBC Type of a List Instance Collection

You can define a collection of instances by creating a List field annotated with @ElementCollection. That means there is another table that stores the collection which has a relationship with the primary table. The collection table is required to have three columns which include the foreign key to the primary table, the element value, and the element index.

If you add the @JdbcTypeCode annotation, it will be applied to the element value column. To set the JDBC type of the element index column, use the @ListIndexJdbcTypeCode annotation.

  @Entity
  @Table(name = "products")
  public class Product {

    @Id
    @UuidGenerator
    private UUID id;
  
    @ElementCollection
    @JdbcTypeCode(SqlTypes.FLOAT)
    @ListIndexJdbcTypeCode(SqlTypes.TINYINT)
    private List<Integer> addedStocks;

    // other columns
  }

In the example above, the JDBC type of the element value is set to FLOAT despite the element type of the field is Integer. Meanwhile, the JDBC type of the element index is set to TINYINT.

Name Type
product_id UUID
added_stocks FLOAT
added_stocks_order TINYINT

Set JDBC Type of a Map Instance Collection

Another way to define a collection of instances is using a Map field. With this way, the collection table must have three columns which include the foreign key to the primary table, the element key, and the element value.

If you add the @JdbcTypeCode annotation, it will be applied to the element value column. To set the type of the element key column, use the @MapKeyJdbcTypeCode annotation.

  @Entity
  @Table(name = "products")
  public class Product {

    @Id
    @UuidGenerator
    private UUID id;
  
    @ElementCollection
    @JdbcTypeCode(SqlTypes.FLOAT)
    @MapKeyJdbcTypeCode(SqlTypes.VARCHAR)
    private Map<Integer, Integer> ratings;

    // other columns
  }

In the example above, the JDBC type of the element value is set to FLOAT despite the element type of the field is Integer. Meanwhile, the JDBC type of the element key is set to VARCHAR.

Name Type
product_id UUID
ratings_key VARCHAR
ratings FLOAT

Set JDBC Type of a Bag Instance Collection

It's also possible to define an instance collection using bag mapping. The stored instances are unordered and allow duplicate elements. There must be three columns in the collection table which include the foreign key to the primary table, the bag ID, and the element value.

If you add the @JdbcTypeCode annotation, it will be applied to the element value column. To set the JDBC type of the bag ID, use the @CollectionIdJdbcTypeCode annotation.

  @Entity
  @Table(name = "products")
  public class Product {

    @Id
    @UuidGenerator
    private UUID id;
  
    @ElementCollection
    @CollectionId(column = @Column( name = "bag_id" ), generator = "increment")
    @CollectionIdJdbcTypeCode(Types.SMALLINT)
    @JdbcTypeCode(SqlTypes.VARCHAR)
    private List<String> keywords;

    // other columns
  }

In the example above, the JDBC type of the element value is set to VARCHAR (actually not necessary because the String is mapped to VARCHAR by default). Meanwhile, the JDBC type of the bag ID is set to SMALLINT.

Name Type
product_id UUID
bag_id SMALLINT
keywords VARCHAR

Set JDBC Type of an Instance Collection of Other Types

For other collections types whose field type is neither List nor Map and not using bag mapping, the @JdbcTypeCode is used to specify the JDBC type of the element column.

  @Entity
  @Table(name = "products")
  public class Product {

    @Id
    @UuidGenerator
    private UUID id;
  
    @ElementCollection
    @JdbcTypeCode(SqlTypes.VARCHAR)
    private Set<LocalDate> restockDates;

    // other columns
  }

In the example above, the collection table has a column named restock_dates whose JDBC type is set to VARCHAR.

Name Type
product_id UUID
restock_dates VARCHAR

Set JDBC Type of a Discriminated Association Mappings

If you define an association mapping using @Any or @ManyToAny, the discriminator type can be set by using the @JdbcTypeCode annotation.

  @Entity
  @Table(name = "products")
  public class Product {

    @Id
    @UuidGenerator
    private UUID id;

    @Any
    @AnyDiscriminator(DiscriminatorType.INTEGER)
    @AnyDiscriminatorValue(discriminator = "1", entity = CreditPayment.class)
    @AnyDiscriminatorValue(discriminator = "2", entity = CashPayment.class)
    @AnyKeyJavaClass(UUID.class)
    @Column(name = "payment_type")
    @JoinColumn(name = "payment_id")
    @JdbcTypeCode(SqlTypes.INTEGER)
    private Payment payment;

    // other columns
  }

In the example above, the products table has a discriminator column named payment_type. The JDBC type of which is set to INTEGER by using the annotation.

Name Type
payment_type INTEGER

Summary

The @JdbcTypeCode annotation can be used to set the JDBC type of a column. When using the annotation, you have to specify the type code to use. Usually, the code is already defined in the Hibernate's SqlTypes class. If an instance collection field uses the annotation, it will be applied on the element value column. The annotation can be applied on the discriminator column of an association mapping as well.

You can also read about: