Enums, dates and AttributeConverter

Hibernate · lesson 20 of 32 · 3 min read

Store enums and custom types in a form that survives the next refactor of your code.

Open this lesson in the learning hub

Key points

  • @Enumerated defaults to ORDINAL, which stores the position. Reorder the enum and every stored row changes meaning.
  • Write @Enumerated(EnumType.STRING) every time. A little wider, unambiguous, and readable in a SQL console.
  • For a short stable code that survives a rename, write an AttributeConverter mapping each constant to a fixed string.
  • @Converter(autoApply = true) applies it to every field of that type, so no entity has to mention the converter.
  • Hibernate 6 maps LocalDate and Instant natively. @Temporal is only for the old java.util.Date.
  • A converter runs on every read and every write of that column. Keep it pure and cheap: no lookups, no I/O, no clock.

Example

public enum OrderStatus { NEW, PAID, SHIPPED }

@Entity
public class Order {

    @Enumerated(EnumType.STRING)          // "PAID" - never the ordinal 1
    @Column(length = 16, nullable = false)
    private OrderStatus status;

    private Instant placedAt;             // mapped natively, no @Temporal

    @Convert(converter = MoneyConverter.class)
    private Money total;                  // stored as a bigint of cents
}

@Converter(autoApply = true)
public class MoneyConverter implements AttributeConverter<Money, Long> {

    @Override
    public Long convertToDatabaseColumn(Money money) {
        return money == null ? null : money.cents();
    }

    @Override
    public Money convertToEntityAttribute(Long cents) {
        return cents == null ? null : Money.ofCents(cents);
    }
}

ORDINAL turns a harmless enum reorder into silent data corruption.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Hibernate course, and every lesson in it is listed on the Hibernate contents page.