| |

How to implement an AttributeConverter to support custom types


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


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.

Supported conversions and limitations

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.

Implementing an AttributeConverter

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.

Activating an AttributeConverter

You can activate an AttributeConverter in 3 ways:

  1. The easiest one is to set the autoApply attribute of the @Converter annotation to true. Your persistence provider will then use the converter for all entity attributes of the given type.
  2. Or you can annotate an entity attribute with the javax.persistence.Convert (JPA 1 & 2) or jakarta.persistence.Convert (JPA 3) annotation and reference your AttributeConverter implementation. Your persistence provider then only uses the converter for that attribute.
    The following code snippet shows an example of this approach:
@Entity
public class Rectangle {

    @Id
    @GeneratedValue
    private Integer id;

    private Integer x;

    private Integer y;

    @Convert(converter = ColorConverter.class)
    private Color color;

    ...
}
  1. Since Hibernate 6.1, you can also use the @ConverterRegistration annotation to register your attribute converter. Using that annotation enables you to separate the implementation of the AttributeConverter from its registration. This enables you to control the active converters in your application, e.g. when using a library that provides multiple converter implementations.
    Here you can see a package-info.java file that registers the previously defined ColorConverter. When using this annotation, you, of course, no longer need to set the autoApply attributer of the @Converter annotation to true.
@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.

The converter gets used transparently

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)
}

Conclusion

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.

29 Comments

  1. Hi Thorben,

    Very helpful article – thank you!

    It seems that a converter is only used when reading an entity from a database, not writing an entity to a database. Is that correct?

    1. Avatar photo Thorben Janssen says:

      Hi Chris,
      the converter is used in both directions. The convertToDatabaseColumn is called during write operations to convert entity attribute’s values and for query parameters. The convertToEntityAttribute is used when reading entity attribute’s values from the database.

  2. Avatar photo Jochen Schmitz says:

    Hi Thorben.

    You already wrote about the exceptions concerning converting an id Attribute. Are there workarounds or official ways
    to Convert e.g. an String to an UUID vice versa?
    We had to switch our underlaying database from postgres to db/2, which does not support UUIDs natively. So we have to convert these right now.

    Kind regards

  3. Avatar photo Oriel-Tzvi Shaer says:

    This helped me.

    Thank-you

  4. Avatar photo Mr Lopmonster Dadio says:

    Nice tut man!

    1. Avatar photo Thorben Janssen says:

      Thanks

  5. charm!

  6. Avatar photo Amit Patil says:

    Will AttributeConverter will get invoked in case of
    1. Static(Named) Or Dynamic Queries using javax.persistence.Query interface and entiyManager
    2. Queries using javax.persistence.Query interface and entiyManager with POJO (Constructor) used to map query result.

    1. Avatar photo Thorben Janssen says:

      Hi Amit,

      yes the AttributeConverter gets applied in all these situations.
      The only situation in which it isn’t used is a native SQL query.

      Regards,
      Thorben

      1. Avatar photo Amit Patil says:

        Great

        Thanks …

  7. Avatar photo Christian Marsch says:

    Saved my day

  8. that’s great (Y)

  9. Hi, I see X and Y values. You mentioned them as well but you never used them in code. Are them optional?

    1. Avatar photo Thorben Janssen says:

      The X and Y were mentioned in the reference to the AttributeConverter interface:

      A Converter must implement the javax.persistence.AttributeConverter interface, where X is the class of the entity representation and Y the class of the database representation of the attribute. Additionally a Converter has to be annotated with the javax.persistence.Converter annotation.

  10. Avatar photo Stefan Bley says:

    Is there any reason why ID attributes can not be converted?
    Hibernate User Types can be used with IDs, though.

    1. Avatar photo Thorben Janssen says:

      Hi Stefan,

      that’s the way it’s defined in the JPA spec. To be honest, I don’t know why they excluded IDs. One reason might be, that the conversion has to be biuniquely and that there is no way the spec or persistence provider can assure that.

      Regards,
      Thorben

  11. Its not working even after changing hibernate-jpa 2.0 module of Jboss to hibernate-jpa-2.1-api-1.0.0.Final.jar… any idea? why its not being called?

    1. Avatar photo Thorben Janssen says:

      Hi Raghu,
      you need to update the used Hibernate module to a version >4.3.0 or switch to Wildfly 8. The hibernate-jpa-2.1-api-1.0.0.Final.jar provides only the JPA 2.1 APIs but not the implementation.
      Regards,
      Thorben

  12. I followed the same steps as above..
    But my Converter never being called.
    I am using hibernate-jpa-2.1-api-1.0.0.Final.jar
    on Jboss 7.1.1 server…

    PLs help me

  13. Avatar photo Anonymous says:

    Hi,

    It's possible to convert an object into more than one column, with converters?

  14. Avatar photo Anonymous says:

    Hi Thorben and others,

    I have a project where I need to create many different (70+) enums and hence repeatedly define 70+ AttributeConverters. Even though using Java8, there is no neat way of doing things at compile time.

    Anyone knows if we could define those converters at runtime?

    Karyn

  15. Avatar photo Anonymous says:

    I see it's very similar to FacesConverter 🙂

  16. Hi Lukas,
    interesting question. I will have a look 🙂
    Regards,
    Thorben

  17. While conversion between entity type and JDBC type is useful for 90% of all use-cases, it is not useful for special cases where the database type is a non-standard type, like PostgreSQL's JSON type, for instance.

    In jOOQ 3.5, we've introduced a new binding SPI, which helps users interact with JDBC bind values on a JDBC level. I wonder if such a thing is possible in JPA as well?

  18. Avatar photo Anonymous says:

    Is there also a way to use the Converter implicitly with a javax.persistence.Query? When I add the Object as a query parameter then I get an Exception.

  19. Hi

    Thanks for the article.

    I am using JBoss AS 7.1. I set the “” in persistence.xml. But it looks like the converer is not getting called in my case. Could you please help for the same. If the request comes from tomcat, then the converter is being called but not if the request is from jboss.

    Your help would be highly appreciable

    Thanks

Comments are closed.