hibernate/hibernate-orm · error · IllegalArgumentException

Unsupported temporal type: %s

Error message

Unsupported temporal type: %s

What it means

Spanner's trunc() routing maps DATE to DATE_TRUNC and TIMESTAMP/TIME to TIMESTAMP_TRUNC; other temporal types (offset/zoned timestamps) have no Spanner function and hit the default branch, throwing IllegalArgumentException at render time. A null temporal type falls through to numeric trunc, which is fine.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/dialect/function/SpannerTruncFunction.java:53

				)
		);
	}

	@Override
	public void render(
			SqlAppender sqlAppender,
			List<? extends SqlAstNode> sqlAstArguments,
			ReturnableType<?> returnType,
			SqlAstTranslator<?> walker) {
		final Expression expression = (Expression) sqlAstArguments.get( 0 );
		final var type = expression.getExpressionType();
		final var temporalType = type != null ? getSqlTemporalType( type ) : null;

		if ( temporalType != null ) {
			switch ( temporalType ) {
				case DATE -> sqlAppender.appendSql( "DATE_TRUNC" );
				case TIMESTAMP, TIME -> sqlAppender.appendSql( "TIMESTAMP_TRUNC" );
				default -> throw new IllegalArgumentException( "Unsupported temporal type: " + temporalType );
			}
			sqlAppender.appendSql( "(" );
			expression.accept( walker );
			sqlAppender.appendSql( ", " );
			sqlAstArguments.get( 1 ).accept( walker );
			sqlAppender.appendSql( ")" );
		}
		else {
			renderNumericTrunc( sqlAppender, sqlAstArguments, walker );
		}
	}

	private void renderNumericTrunc(
			SqlAppender sqlAppender,
			List<? extends SqlAstNode> args,
			SqlAstTranslator<?> walker) {
		sqlAppender.appendSql( "TRUNC(" );
		args.get( 0 ).accept( walker );

View on GitHub (pinned to fad1729dce)

Solutions

  1. Cast the argument: trunc(cast(e.offsetTs as timestamp), MONTH)
  2. Remap the attribute to LocalDateTime for Spanner
  3. Upgrade Hibernate for wider Spanner temporal coverage

Example fix

// before
select trunc(e.offsetTs, MONTH) from Event e

// after
select trunc(cast(e.offsetTs as timestamp), MONTH) from Event e
Defensive patterns

Strategy: type-guard

Validate before calling

// Spanner trunc() only accepts DATE/TIME/TIMESTAMP expressions
static void checkSpannerTruncArg(Class<?> attrType) {
    if (OffsetDateTime.class.equals(attrType) || ZonedDateTime.class.equals(attrType)
            || OffsetTime.class.equals(attrType)) {
        throw new IllegalArgumentException("Cast offset/zoned temporals to timestamp for Spanner: " + attrType);
    }
}

Type guard

static boolean spannerTemporalSafe(Class<?> t) {
    return !(OffsetDateTime.class.equals(t) || ZonedDateTime.class.equals(t) || OffsetTime.class.equals(t));
}

Try / catch

try {
    return em.createQuery(hql, Object.class).getResultList();
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("Unsupported temporal type")) {
        return em.createQuery(withTimestampCast(hql), Object.class).getResultList();
    }
    throw e;
}

Prevention

When it happens

Trigger: HQL: trunc(OffsetDateTime-attribute) or trunc(ZonedDateTime-attribute) on the Cloud Spanner dialect.

Common situations: Date-bucketing queries (day/week/month rollups) over attributes mapped as offset/zoned types; models ported to Spanner without remapping temporal attributes.

Related errors


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