All JPA implementations, including Hibernate, provide default mappings for a huge set of standard Java classes. You could model the attributes of all your entity classes using those mappings, and you would be able to map all columns of your table model. But this is often not the best fit for your business logic. A mapping to a domain-specific type or any other Java type that adds semantics to your entity attribute is often more helpful. You could, for example, map a column of type varchar to your domain-specific type Name, which extends the standard class String and adds domain-specific validation. Or you could map a 8-digit hexadecimal color code to an object of type java.awt.Color.
JPA’s AttributeConverter interface provides an easy way to define such a mapping. You only need to implement the 2 methods defined by the interface and specify for which entity attributes you want to use the converter. I’ll show you how to do that in this article.
The general concept of the AttributeConverter is simple. The 2 methods of the AttributeConverter interface define 2 conversions. One that converts the type used for your entity attribute to a type that gets handled by the JDBC driver when inserting or updating a record in the database. And another one that converts the type returned by the JDBC driver when reading a record from the database to the type used as your entity attribute.
Based on this simple concept, the capabilities and limitations of an attribute converter become obvious.
You can use it on all basic attributes mapped to 1 column in your table model and defined by entity classes, mapped superclasses, or embeddable classes.
But the converter can’t handle more complex types, like an entire ElementCollection, a to-many association, or any attribute you want to map to multiple database columns. You can also not use an AttributeConverter on primary key attributes or version attributes. The JPA specification defines a specific handling for those attributes, which could cause conflicts. And attributes that are annotated with @Temporal or @Enumerated are also not supported. That’s because those annotations already define a mapping to a database column. You need to decide if you want to use the AttributeConverter or the other type mapping and only add the corresponding annotations.
The list of situations in which you can’t use an AttributeConverter might seem much longer than the one in which you can use it. But don’t worry, the AttributeConverter is incredibly useful and can handle almost all standard use cases.
Let’s implement an AttributeConverter that converts between an entity attribute of type java.awt.Color and a String containing a 6-digit hex value.
Implementing an AttributeConverter requires a class that implements the javax.persistence.AttributeConverter (JPA 1 & 2) or jakarta.persistence.AttributeConverter (JPA 3) interface. Besides the package name, those 2 interfaces are identical. As you can see in the code snippet, the AttributeConverter interface uses generics. Those are the type of the entity attribute and the type handled by the JDBC driver. In this example, the attribute will be of type Color and the JDBC driver will handle a String.
@Converter(autoApply = true)
public class ColorConverter implements AttributeConverter<Color, String> {
Logger log = LogManager.getLogger(this.getClass().getName());
@Override
public String convertToDatabaseColumn(Color attribute) {
String hex = "#"+Integer.toHexString(attribute.getRGB()).substring(0,6);
log.info("Convert "+attribute+" to "+hex);
return hex;
}
@Override
public Color convertToEntityAttribute(String dbData) {
Color color = Color.decode(dbData);
log.info("Convert "+dbData+" to "+color);
return color;
}
}
And you also need to annotate your converter class with JPA’s @Converter annotation. The @Converter annotation tells your persistence provider, e.g., Hibernate, that this is an attribute converter. And you can set its autoApply attribute to true if you want to use this converter for all entity attributes of type Color. If you don’t want to do that, please check the following section, where I show you how to activate the converter for a specific attribute.
The implementation of the AttributeConverter is pretty simple. The interface defines the methods convertToDatabaseColumn and convertToEntityAttribute. Hibernate and any other JPA implementation call these methods to either convert the value of your entity attribute to the type handled by the JDBC driver or vice versa.
You can activate an AttributeConverter in 3 ways:
@Entity
public class Rectangle {
@Id
@GeneratedValue
private Integer id;
private Integer x;
private Integer y;
@Convert(converter = ColorConverter.class)
private Color color;
...
}
@org.hibernate.annotations.ConverterRegistration(converter=com.thorben.janssen.model.ColorConverter.class, autoApply=true)
package com.thorben.janssen.model;
That’s all you need to do to implement an AttributeConverter that provides a custom type mapping.
After you activate the AttributeConverter for an attribute, your persistence provider uses the converter transparently for all operations that affect that entity attribute. That includes all read and write operations performed for that entity class and all bind parameters compared with that attribute.
You can see that in the following example. It reads a Rectangle entity object with the color white and changes its color to black.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Rectangle r = em.createQuery("SELECT r FROM Rectangle r WHERE r.color = :color", Rectangle.class)
.setParameter("color", Color.WHITE)
.getSingleResult();
r.setColor(Color.BLACK);
em.getTransaction().commit();
em.close();
I used Hibernate as my JPA implementation for the following log output and activated my recommended logging configuration for development systems. You can see the executed SQL statements and the messages written by the AttributeConverter implementation in the log file.
19:11:37,114 INFO [com.thorben.janssen.model.ColorConverter] - Convert java.awt.Color[r=255,g=255,b=255] to #ffffff
19:11:37,170 DEBUG [org.hibernate.SQL] - select r1_0.id,r1_0.color,r1_0.x,r1_0.y from Rectangle r1_0 where r1_0.color=?
19:11:37,171 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [1] as [VARCHAR] - [#ffffff]
19:11:37,179 INFO [com.thorben.janssen.model.ColorConverter] - Convert #ffffff to java.awt.Color[r=255,g=255,b=255]
19:11:37,181 INFO [com.thorben.janssen.model.ColorConverter] - Convert java.awt.Color[r=255,g=255,b=255] to #ffffff
19:11:37,181 INFO [com.thorben.janssen.model.ColorConverter] - Convert #ffffff to java.awt.Color[r=255,g=255,b=255]
19:11:37,184 DEBUG [org.hibernate.stat.internal.StatisticsImpl] - HHH000117: HQL: SELECT r FROM Rectangle r WHERE r.color = :color, time: 39ms, rows: 1
19:11:37,192 DEBUG [org.hibernate.SQL] - update Rectangle set color=?, x=?, y=? where id=?
19:11:37,193 INFO [com.thorben.janssen.model.ColorConverter] - Convert java.awt.Color[r=0,g=0,b=0] to #ff0000
19:11:37,193 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [1] as [VARCHAR] - [#ff0000]
19:11:37,193 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [2] as [INTEGER] - [10]
19:11:37,193 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [3] as [INTEGER] - [20]
19:11:37,193 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [4] as [INTEGER] - [1]
19:11:37,196 INFO [com.thorben.janssen.model.ColorConverter] - Convert java.awt.Color[r=0,g=0,b=0] to #ff0000
19:11:37,196 INFO [com.thorben.janssen.model.ColorConverter] - Convert #ff0000 to java.awt.Color[r=255,g=0,b=0]
19:11:37,203 INFO [org.hibernate.engine.internal.StatisticalLoggingSessionEventListener] - Session Metrics {
31200 nanoseconds spent acquiring 1 JDBC connections;
26100 nanoseconds spent releasing 1 JDBC connections;
191100 nanoseconds spent preparing 2 JDBC statements;
4859600 nanoseconds spent executing 2 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
13747100 nanoseconds spent executing 1 flushes (flushing a total of 1 entities and 0 collections);
770600 nanoseconds spent executing 1 partial-flushes (flushing a total of 0 entities and 0 collections)
}
An AttributeConverter provides an easy and portable way to define a custom type mapping. You can use it for all basic attributes you want to map to 1 database column. In this article, I used that to to persist an entity attribute of type java.awt.Color as a 6-digit hex code. But that’s, of course, not the only kind of mapping you can implement. I used it in other articles to improve Hibernate’s standard enum mapping and to map LocalDate and LocalDateTime in older Hibernate versions that didn’t support those types.
As you saw in this article, implementing an AttributeConverter is simple. You only need to implement the AttributeConverter interface with its 2 conversion methods and annotate that class with a @Converter annotation. If you set the autoApply attribute of that annotation to true, your persistence provider will use the converter for all entity attributes of the supported type. If you don’t set that attribute or set it to false, you need to annotate each entity attribute on which you want to use the converter with @Convert and reference your converter implementation.