Spring and JPA: use custom value types inside entities with a custom converter

If you want secure code, or code with fewer bugs, you want to track the kind of data you’re passing around. In your code, do not pass around strings or any other built-in built-in primitive type. Those are for the edges: we have to accept strings as input, we have to write VARCHAR to the database. In between, we don’t have strings! We have Usernames, Money, SKUs, PhoneNumbers, UnencryptedPasswords, etc.

Instead of language primitives, use domain primitives.

Domain primitives are wrapper types that refuse to be constructed with invalid data inside them. In a statically typed language, these make sure you never swap “price” with “amount” (both numbers) or “first name” with “secret answer” (both strings). You won’t accidentally print a credit card number to a log if its toString() masks the digits. Rest assured that every PurchaseQuantity is above zero. Follow sensitive data through your code with usages of MedicalDiagnosis. Let the “String” or “int” primitive be an implementation detail inside a proper class.

In my Spring Boot app, using JPA and Hibernate and all the default magic persistence stuff, this got hard. My entity classes, which magically get stored and retrieved, must use primitive types. Otherwise JPA doesn’t know how to persist the fields.

Until! Today I finally found the secret! Now I can tell JPA how to wrap and unwrap primitive types into meaningful value classes! Here it is:

Make a little AttributeConverter, and tell Spring to autoApply the Converter. In my example, a CatName class wraps a String. (code)


package com.jessitron.catdiary.cats;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class CatNameConverter implements AttributeConverter<CatName, String> {
  @Override
  public String convertToDatabaseColumn(CatName domainPrimitive) {
    return domainPrimitive.getName();
  }

  @Override
  public CatName convertToEntityAttribute(String dbData) {
    return new CatName(dbData);
  }
}

The two little methods in the converter tell Spring JPA how to get the String out of the CatName and a CatName from a String. That’s exactly what I wanted to tell it.

Now I can include CatNames in my entities (code) and they’re magically stored as Strings and everything is happy and glorious.