Explain how a JPA AttributeConverter works, how you attach one to an attribute with @Convert versus applying it automatically, and which kinds of attributes you are not allowed to convert.
answer
- two methods: toDatabaseColumn / toEntityAttribute
- @Convert per attribute vs @Converter(autoApply = true)
- attributeName targets inside embeddables; disableConversion opts out
- banned on @Id, @Version, associations, @Enumerated/@Temporal
- stateless, thread-safe, null-safe; one column only
basics
~20 sAttributeConverter<X,Y> defines convertToDatabaseColumn and convertToEntityAttribute, translating between a Java type and a column type. Attach it per attribute with @Convert, or globally for the type with @Converter(autoApply = true). Identifiers, version attributes and associations cannot be converted.
solid answer
~50 sYou implement `AttributeConverter<EntityType, DatabaseType>` with two methods: `convertToDatabaseColumn` (called when binding a value) and `convertToEntityAttribute` (called when reading a column). The database side must be a type the provider already knows how to persist — `String`, `Integer`, `byte[]`, and so on. Wiring it up has two modes. `@Converter(autoApply = true)` on the converter class applies it to **every** attribute of that Java type across the persistence unit. Otherwise you attach it per attribute with `@Convert(converter = MyConverter.class)`, and `@Convert(attributeName = "…")` reaches into an embeddable. `@Convert(disableConversion = true)` opts one attribute out of an auto-applied converter. The restrictions: no converter on identifiers, on the version attribute, on relationship attributes, or on anything already annotated `@Enumerated` or `@Temporal`. Converters must be stateless and thread-safe — one instance serves all threads — and they can be handed `null` in both directions, so guard for it.
code
java · 26 lines@Converter(autoApply = true)
public class MoneyConverter implements AttributeConverter<Money, BigDecimal> {
@Override
public BigDecimal convertToDatabaseColumn(Money attribute) {
return attribute == null ? null : attribute.amount();
}
@Override
public Money convertToEntityAttribute(BigDecimal dbData) {
return dbData == null ? null : Money.of(dbData);
}
}
@Entity
class Order {
@Id Long id;
Money total; // auto-applied
@Convert(converter = YesNoBooleanConverter.class)
boolean active; // explicit
@Convert(disableConversion = true)
Money rawTotal; // opted out
}go deeper
Know the interface's two methods and that @Convert attaches a converter to a field; be able to write a simple Y/N boolean converter.
Explain autoApply versus explicit attachment, attributeName for embeddables, disableConversion, and the forbidden attribute kinds.
Add the operational concerns — statelessness and thread safety, null handling in both directions, and the fact that conversion sits at binding time so native SQL bypasses it.
Decide when a type-wide auto-applied converter is the right seam versus an embeddable, a custom type, or keeping the translation out of persistence entirely, and weigh the queryability cost of the chosen stored form.
## The interface ```java public interface AttributeConverter<X, Y> { Y convertToDatabaseColumn(X attribute); X convertToEntityAttribute(Y dbData); } ``` `X` is the Java type on the entity; `Y` is the type written to the column, and it must be something the provider can already persist natively. The converter sits between the entity attribute and the JDBC binding: on write, the provider calls `convertToDatabaseColumn` and binds the result; on read, it pulls `Y` out of the result set and calls `convertToEntityAttribute`. That is the whole mechanism — a two-way function on a basic attribute. Typical uses: a value object (`Money`, `EmailAddress`, `PhoneNumber`) stored as text; a boolean stored as `'Y'`/`'N'` in a legacy schema; an enum stored as a stable code rather than its name or ordinal; a set of flags packed into a string; encryption or tokenisation of a single column. ## Wiring: explicit versus automatic **Explicit** — annotate the attribute: ```java @Convert(converter = YesNoBooleanConverter.class) private boolean active; ``` This is precise and greppable: a reader of the entity can see that something happens to this column. **Automatic** — annotate the converter class: ```java @Converter(autoApply = true) public class MoneyConverter implements AttributeConverter<Money, BigDecimal> { … } ``` Now every `Money` attribute in the persistence unit is converted without further annotation. This is right when the mapping is a property of the type and you want it uniformly — it removes the chance of someone forgetting `@Convert` on a new field. It is wrong when the same Java type must be stored differently in different places, because you then need `@Convert` overrides sprinkled around, or `@Convert(disableConversion = true)` to opt out. **Reaching inside structures.** `@Convert(attributeName = "amount", converter = …)` on an embedded attribute targets one attribute inside the embeddable, which is how you convert one field of a shared `@Embeddable` without changing the embeddable itself. For an `@ElementCollection` of basic values, `@Convert` on the collection attribute applies to the elements. For a `Map`, `attributeName` accepts the `key` prefix to target keys. Registration in plain JPA is by discovery: the converter class must be visible to the persistence unit (scanned, or listed in `orm.xml`). The provider instantiates it — in plain JPA that means a no-arg constructor and no injection, so a converter that needs collaborators (an encryption key, for instance) has to obtain them itself. ## Where converters are forbidden The specification excludes: - **Identifier attributes** (`@Id`, and the attributes of an `@EmbeddedId`/`@IdClass`). Identity participates in the persistence context's keying and in generation, and conversion is not permitted there. - **The version attribute** (`@Version`). Its type set is fixed and it is compared by the provider. - **Relationship attributes.** Associations are mapped by foreign keys, not by value conversion. - **Attributes annotated `@Enumerated` or `@Temporal`.** These already declare a conversion; you must choose one mechanism or the other. Converting an enum is legal and common — just do not also annotate it `@Enumerated`. Some providers relax parts of this, but relying on that is a portability bet. ## Lifecycle, threading and nulls A converter instance is shared. It must be stateless and thread-safe: no mutable fields, no caching keyed on the last call, no non-thread-safe helpers such as an unsynchronised date formatter. If you need a heavyweight helper, make it immutable and thread-safe, or create it per call. Nulls reach the converter. `convertToDatabaseColumn(null)` and `convertToEntityAttribute(null)` are both possible, and an unguarded implementation produces a `NullPointerException` deep inside the flush — an ugly stack trace far from the cause. Handle null explicitly and decide what it means in each direction; note that a converter that turns Java `null` into a non-null database value changes the meaning of `IS NULL` in queries. ## Where conversion happens — and where it does not Conversion is part of value binding and result extraction. That means it applies to entity state on flush and load, and to **bound query parameters** for the converted attribute. It does **not** apply to native SQL, which bypasses the mapping entirely, and it cannot help expressions the database evaluates over the stored form. That is the source of most converter surprises when querying. ## Choosing a converter over the alternatives Use a converter when the transformation belongs to the *type* and is a pure function in both directions. Prefer a custom provider-specific type when you need control over the JDBC binding itself, an `@Embeddable` when the value spans multiple columns (converters are strictly one attribute to one column), and property access when the translation is specific to one entity rather than the type.
- Why can't you attach an AttributeConverter to an @Id attribute?The specification forbids it. Identifiers drive the persistence context's keying, identity generation and foreign-key handling, so the provider needs the value in its native mapped form throughout. If the stored key really has a different shape, model it with a mapped identifier type such as an @EmbeddedId or handle the translation outside persistence.
- What happens if an AttributeConverter keeps mutable state in a field?The provider creates a single instance and uses it from every thread that touches the mapping, so mutable state produces race conditions and cross-request data bleed — a value converted for one transaction leaking into another. Converters must be stateless and thread-safe, and any helper they hold must be too, which is why an unsynchronised date formatter in a converter is a classic bug.
- Is convertToDatabaseColumn ever called with null?Yes, in both directions — a null attribute on write and a null column on read. An unguarded implementation throws a NullPointerException inside the flush or the result extraction, far from the code that caused it. Guard both methods explicitly, and be aware that converting Java null into a non-null stored value changes what IS NULL means in queries.
A converter is a currency exchange booth at the border: everything crossing in either direction is exchanged automatically, but anything that tunnels under the border — native SQL — arrives in the original currency.
saying these in an interview costs you the question
- Claiming a converter can be applied to the @Id or @Version attribute.
- Combining @Convert with @Enumerated or @Temporal on the same attribute.
- Writing a converter with mutable fields or a shared non-thread-safe formatter.
- Assuming converters run for native SQL statements.
- Believing a converter can map one attribute onto several columns.