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

  1. 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).
  2. If the field is a date, map it as LocalDate (no @Temporal) so the TIME/TIMESTAMP literal path is not taken.
  3. Convert non-ISO chronology values with `LocalDate.from(temporalAccessor)` at the API boundary.
  4. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/57ee9c7c6ddf3de9. Report an issue: GitHub.