hibernate/hibernate-orm · error · UnsupportedOperationException
"Unsupported temporal type: " + temporalAccessor.getClass().
Error message
"Unsupported temporal type: " + temporalAccessor.getClass().getName()
What it means
SpannerPostgreSQLDialect.appendDateTimeLiteral(TemporalAccessor, ...) throws UnsupportedOperationException when the literal must be normalized (precision is TIME, or TIMESTAMP without an offset — i.e. !isSupported(ChronoField.OFFSET_SECONDS)) but the value is not one of LocalTime, OffsetTime, LocalDateTime, or Instant. The dialect needs an OffsetDateTime-shaped literal for Spanner PG's timestamptz semantics; exotic TemporalAccessor implementations (Year, YearMonth, MonthDay, non-ISO chronologies like JapaneseDate, or a LocalDate fed through the TIME/TIMESTAMP path) cannot be converted, so it names the offending class and throws.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/dialect/SpannerPostgreSQLDialect.java:635
TemporalType precision,
TimeZone jdbcTimeZone) {
if ( precision == TemporalType.TIME || (precision == TemporalType.TIMESTAMP && !temporalAccessor.isSupported( ChronoField.OFFSET_SECONDS ))) {
precision = TemporalType.TIMESTAMP;
if ( temporalAccessor instanceof LocalTime localTime) {
temporalAccessor = localTime.atDate( LocalDate.of( 1970, 1, 1 ) )
.atOffset( ZoneOffset.UTC );
}
else if ( temporalAccessor instanceof OffsetTime offsetTime ) {
temporalAccessor = offsetTime.atDate( LocalDate.of( 1970, 1, 1 ) );
}
else if ( temporalAccessor instanceof LocalDateTime localDateTime) {
temporalAccessor = localDateTime.atOffset( ZoneOffset.UTC );
}
else if ( temporalAccessor instanceof Instant instant) {
temporalAccessor = instant.atOffset( ZoneOffset.UTC );
}
else {
throw new UnsupportedOperationException( "Unsupported temporal type: " + temporalAccessor.getClass().getName() );
}
}
super.appendDateTimeLiteral( appender, temporalAccessor, precision, jdbcTimeZone );
}
@Override
public void appendDateTimeLiteral(
SqlAppender appender,
Date date,
@SuppressWarnings("deprecation")
TemporalType precision,
TimeZone jdbcTimeZone) {
if ( precision == TemporalType.TIME ) {
precision = TemporalType.TIMESTAMP;
}
super.appendDateTimeLiteral( appender, date, precision, jdbcTimeZone );
}View on GitHub (pinned to fad1729dce)
Solutions
- Normalize parameters to supported types before binding: convert LocalDate/Year/YearMonth → LocalDate or LocalDateTime, ZonedDateTime → Instant/OffsetDateTime, non-ISO dates → LocalDate.atTime(...) via LocalDate.from(temporal).
- If the field is a date, map it as LocalDate (no @Temporal) so the TIME/TIMESTAMP literal path is not taken.
- Convert non-ISO chronology values with `LocalDate.from(temporalAccessor)` at the API boundary.
- As a last resort, format the value to a String and parse it in SQL (cast(... as timestamptz)) instead of binding a TemporalAccessor.
Example fix
// before
TemporalAccessor v = JapaneseDate.now(); // non-ISO, unsupported
q.setParameter("d", v);
// after
q.setParameter("d", LocalDate.from(v)); // normalized to supported type Defensive patterns
Strategy: type-guard
Type guard
static OffsetDateTime toSpannerLiteral(TemporalAccessor t) {
if (t instanceof Instant i) return i.atOffset(ZoneOffset.UTC);
if (t instanceof LocalDateTime ldt) return ldt.atOffset(ZoneOffset.UTC);
if (t instanceof OffsetDateTime odt) return odt;
if (t instanceof ZonedDateTime zdt) return zdt.toOffsetDateTime();
if (t instanceof LocalDate ld) return ld.atStartOfDay(ZoneOffset.UTC).toOffsetDateTime();
if (t instanceof LocalTime lt) return lt.atDate(LocalDate.EPOCH).atOffset(ZoneOffset.UTC);
if (t instanceof OffsetTime ot) return ot.atDate(LocalDate.EPOCH);
throw new IllegalArgumentException("Unsupported temporal type for Spanner PG literal: " + t.getClass().getName());
} Try / catch
try {
q.setParameter("d", value).getResultList();
} catch (UnsupportedOperationException e) {
if (e.getMessage().startsWith("Unsupported temporal type:")) {
q.setParameter("d", LocalDate.from(value)).getResultList(); // normalize and retry
} else throw e;
} Prevention
- Bind java.time types Spanner PG handles (Instant, OffsetDateTime, LocalDateTime, LocalTime, LocalDate) instead of raw TemporalAccessor.
- Convert non-ISO chronology values (JapaneseDate etc.) with LocalDate.from(...) at the API boundary.
- Never map date fields with @Temporal(TemporalType.TIME).
- Type query parameters concretely; avoid TemporalAccessor in repository signatures.
When it happens
Trigger: Passing a temporal literal parameter that is a non-ISO-chronology date (e.g. JapaneseDate/ThaiBuddhistDate), java.time.Year/YearMonth/MonthDay, or a LocalDate under TemporalType.TIME, as a query parameter bound for literal rendering: `q.setParameter("d", value)` with rendering as a datetime literal on SpannerPostgreSQLDialect.
Common situations: Locale-aware apps using non-ISO calendars (Japanese era dates) persisted through Hibernate; APIs that accept broad TemporalAccessor-typed fields and forward them to queries; mapping a LocalDate attribute with @Temporal(TemporalType.TIME) by mistake; JDBC drivers that hand back non-standard temporal implementations.
Related errors
- Spanner doesn't support for-update with no-wait timeout
- Spanner doesn't support for-update with skip locked timeout
- Spanner does not support lock timeout.
- Spanner does not support skip locked.
- Spanner does not support no wait.
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/57ee9c7c6ddf3de9.
Report an issue: GitHub.