skip to content

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?

level: middleimportance: should knowfreq 36%

answer

  1. @Temporal = only for java.util.Date / Calendar
  2. DATE / TIME / TIMESTAMP — lossy at write time
  3. java.time is self-describing → annotation forbidden
  4. java.sql.* also needs no annotation
  5. 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
java
@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

for a junior

Know it exists for java.util.Date/Calendar, that it selects DATE, TIME or TIMESTAMP, and that java.time needs nothing.

for a middle

Explain why the legacy types are ambiguous, that the choice is lossy, and that applying it to java.time is a specification violation.

for a senior

Describe migrating a legacy model: same column, changed field type, deliberate zone semantics, precision review, and dropped defensive copies.

for a principal

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'

context