hibernate/hibernate-orm · error · IllegalArgumentException

Illegal attempt to treat `java.sql.Time` as `java.sql.Date`

Error message

Illegal attempt to treat `java.sql.Time` as `java.sql.Date`

What it means

JdbcTimeJavaType is Hibernate's descriptor for TIME-precision values (java.sql.Time). Its unwrap() converts a Time to Calendar, Timestamp, LocalTime and others, but deliberately rejects java.sql.Date: converting a time-only value into a date-only value discards the time and is not a valid conversion. The IllegalArgumentException is thrown whenever a caller asks to unwrap a Time value as java.sql.Date.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeJavaType.java:173

			return type.cast( value.getTime() );
		}

		if ( String.class.isAssignableFrom( type ) ) {
			return type.cast( toString( value ) );
		}

		if ( Calendar.class.isAssignableFrom( type ) ) {
			final var gregorianCalendar = new GregorianCalendar();
			gregorianCalendar.setTimeInMillis( value.getTime() );
			return type.cast( gregorianCalendar );
		}

		if ( java.sql.Timestamp.class.isAssignableFrom( type ) ) {
			return type.cast( new java.sql.Timestamp( value.getTime() ) );
		}

		if ( java.sql.Date.class.isAssignableFrom( type ) ) {
			throw new IllegalArgumentException( "Illegal attempt to treat `java.sql.Time` as `java.sql.Date`" );
		}

		throw unknownUnwrap( type );
	}

	private static LocalTime unwrapLocalTime(Time value) {
		final var localTime = value.toLocalTime();
		long millis = value.getTime() % 1000;
		if ( millis == 0 ) {
			return localTime;
		}
		if ( millis < 0 ) {
			// The milliseconds for a Time could be negative,
			// which usually means the time is in a different time zone
			millis += 1_000L;
		}
		return localTime.with( ChronoField.NANO_OF_SECOND, millis * 1_000_000L );
	}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Align temporal precision: the same @Temporal on the entity field and every query parameter binding for it
  2. Move to java.time types (LocalTime for times) which map unambiguously
  3. If a Date is genuinely needed from a Time value, convert manually with new java.sql.Date(time.getTime()) instead of unwrap
  4. Check custom UserType/AttributeConverter code for unwrap() calls requesting java.sql.Date while handling time values

Example fix

// before
@Temporal(TemporalType.TIME)
private java.util.Date start;
...
query.setParameter("p", entity.getStart(), TemporalType.DATE); // IllegalArgumentException

// after
query.setParameter("p", entity.getStart(), TemporalType.TIME);
// better: private LocalTime start;
Defensive patterns

Strategy: type-guard

Validate before calling

if (value instanceof java.sql.Time) {
    // TIME-precision value: bind with TemporalType.TIME only, never DATE
}

Type guard

static boolean isTimePrecision(Object v) {
    return v instanceof java.util.Date && !(v instanceof java.sql.Date);
}

Try / catch

try {
    query.setParameter("p", value, TemporalType.TIME);
} catch (IllegalArgumentException e) {
    throw new IllegalStateException("Temporal precision mismatch: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: unwrap(time, java.sql.Date.class, options) reached when an attribute or parameter has @Temporal(TemporalType.DATE) but the bound value is managed by the TIME descriptor (e.g. setParameter(name, javaSqlTimeValue, TemporalType.DATE)); a java.sql.Time value bound to a DATE-mapped parameter; a custom UserType calling unwrap on JdbcTimeJavaType with java.sql.Date.class.

Common situations: Legacy java.util.Date fields queried with mismatched TemporalType; Hibernate 5 to 6 migrations where temporal type resolution changed; shared DTOs that reuse one field for both time and date semantics.

Related errors


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