What is the JPA @Temporal annotation for, when is it required, and why should it not appear on attributes typed with the java.time API?
answer
- @Temporal = only for java.util.Date / Calendar
- DATE / TIME / TIMESTAMP — lossy at write time
- java.time is self-describing → annotation forbidden
- java.sql.* also needs no annotation
- Migration: change type, drop annotation, watch precision
basics
~20 s@Temporal exists only for the legacy types java.util.Date and java.util.Calendar, which are ambiguous: it tells the provider whether to map DATE, TIME or TIMESTAMP. java.time types already carry that meaning, so @Temporal is unnecessary and not permitted on them.
solid answer
~50 s`java.util.Date` and `java.util.Calendar` each represent a full millisecond timestamp, so a provider cannot tell whether you meant a SQL `DATE`, `TIME` or `TIMESTAMP`. `@Temporal(TemporalType.DATE|TIME|TIMESTAMP)` resolves that ambiguity, and JPA **requires** it on those two types. The java.time types are already unambiguous: `LocalDate` means DATE, `LocalTime` means TIME, `LocalDateTime`/`Instant`/`OffsetDateTime` mean timestamp. The specification therefore says `@Temporal` must **not** be applied to them; Hibernate treats it as an error or ignores it depending on version, and either way it signals a candidate copying old tutorials. `java.sql.Date`, `java.sql.Time` and `java.sql.Timestamp` also need no annotation, since the Java type already names the SQL type. In new code the right move is simply not to use `java.util.Date` in entities. Where it exists in a legacy model, the usual migration is to change the field type to the matching java.time type and drop `@Temporal` — no schema change is needed as long as the SQL type stays the same.
code
java · 14 lines@Entity
public class LegacyPerson {
@Temporal(TemporalType.DATE)
private java.util.Date birthDate; // required: Date is ambiguous
@Temporal(TemporalType.TIMESTAMP)
private java.util.Calendar lastLogin; // required
}
@Entity
public class Person {
private LocalDate birthDate; // DATE, no annotation
private Instant lastLogin; // timestamp, no annotation
}go deeper
Know it exists for java.util.Date/Calendar, that it selects DATE, TIME or TIMESTAMP, and that java.time needs nothing.
Explain why the legacy types are ambiguous, that the choice is lossy, and that applying it to java.time is a specification violation.
Describe migrating a legacy model: same column, changed field type, deliberate zone semantics, precision review, and dropped defensive copies.
Position java.util.Date in entities as technical debt with a real cost — implicit zone conversion and mutability — and plan its removal as part of a broader time-handling convention.
## The ambiguity @Temporal solves `java.util.Date` is a single moment in time to millisecond precision — despite its name it is not a date. `java.util.Calendar` is similar with more machinery. When such a field is persisted, the provider has to pick a JDBC binding, and three are plausible: - `java.sql.Date` → SQL `DATE` (time discarded) - `java.sql.Time` → SQL `TIME` (date discarded) - `java.sql.Timestamp` → SQL `TIMESTAMP` (both kept, plus fractional seconds) JPA makes the developer choose: ```java @Temporal(TemporalType.DATE) private java.util.Date birthDate; // stores 1990-04-01, time dropped @Temporal(TemporalType.TIMESTAMP) private java.util.Date createdAt; // stores date + time ``` Omitting `@Temporal` on a `java.util.Date` or `Calendar` attribute is a mapping error; Hibernate has historically defaulted to `TIMESTAMP` in some paths, but relying on that is not portable and hides intent. A subtlety worth knowing: the choice is **lossy at write time**. `TemporalType.DATE` truncates the time-of-day permanently. Teams have shipped bugs where an audit column mapped `DATE` while the code assumed full timestamps, so all events on a day appeared simultaneous. ## Why java.time makes it obsolete JPA 2.2 (2017) added required support for `LocalDate`, `LocalTime`, `LocalDateTime`, `OffsetTime` and `OffsetDateTime`; Hibernate additionally supports `Instant`, `ZonedDateTime`, `Duration` and `Year`. Each of these already *is* the semantic distinction `@Temporal` was invented to supply: | Java type | SQL type | |---|---| | `LocalDate` | `DATE` | | `LocalTime` / `OffsetTime` | `TIME` (with zone variant) | | `LocalDateTime` | `TIMESTAMP` (no zone) | | `Instant` / `OffsetDateTime` | timestamp, UTC-normalised or with-zone | There is nothing left to disambiguate, so the specification states `@Temporal` must not be applied to java.time attributes. Putting it there is at best noise and at worst a bootstrap failure. The same applies to `java.sql.Date`/`Time`/`Timestamp`, whose Java type already names the SQL type. ## Migrating a legacy model The mechanical part is small: ```java // before @Temporal(TemporalType.TIMESTAMP) private java.util.Date createdAt; // after private Instant createdAt; ``` No schema change is required when the SQL type is unchanged — a `TIMESTAMP` column serves both. What does need care: - **Semantics.** `java.util.Date` was implicitly interpreted through the JVM default zone by the JDBC driver. Moving to `Instant` makes the moment explicit; moving to `LocalDateTime` keeps wall-clock semantics. Choose deliberately, because the two disagree the moment the server zone changes. - **Precision.** `java.util.Date` is millisecond-precision; java.time carries nanoseconds. Declare column precision (e.g. `TIMESTAMP(6)`) so nothing is silently truncated, and expect equality assertions in tests to need truncation. - **API surface.** DTOs, JSON serialisation and query parameters all change type; `Date` comparisons scattered through the code become `Instant`/`LocalDate` comparisons, which usually clarifies latent bugs rather than creating them. - **Mutability.** `java.util.Date` is mutable, so entities often defensively copied it. java.time types are immutable, and those copies can go. ## What to say in an interview "`@Temporal` disambiguates `java.util.Date`/`Calendar` into DATE, TIME or TIMESTAMP. It is mandatory for those types and forbidden for java.time, which is self-describing. In new code I would not use `java.util.Date` in an entity at all; in legacy code I would migrate the field type and drop the annotation, watching the zone semantics and the column's fractional-second precision." That answer shows both the rule and the judgement behind it.
- What happens if you omit @Temporal on a java.util.Date attribute?It is a mapping error under the specification. In practice Hibernate has often defaulted to `TemporalType.TIMESTAMP`, so code may appear to work, but the behaviour is neither portable nor self-documenting — and if the intended column was a DATE, you get an unexpected type mismatch or an unintended time component. Always annotate legacy Date/Calendar fields explicitly, or better, migrate the type.
- Is a schema migration needed when changing a field from @Temporal(TIMESTAMP) java.util.Date to Instant?Usually not: both bind to a SQL timestamp column, so the column stays as it is. What you should review is the zone semantics — the old mapping went through the JVM default zone via the JDBC driver, while `Instant` is an explicit UTC moment — and the column's fractional-second precision, since java.time offers nanoseconds where `java.util.Date` had milliseconds.
saying these in an interview costs you the question
- Adding @Temporal to LocalDate/LocalDateTime/Instant attributes
- Thinking @Temporal controls the time zone of a timestamp
- Believing @Temporal(DATE) merely formats output rather than truncating the stored value
- Claiming java.time requires an AttributeConverter in modern JPA
- Assuming omitting @Temporal on java.util.Date is portable because 'it works on Hibernate'