hibernate/hibernate-orm · error · AuditException

No changeset exists at or before the given date

Error message

No changeset exists at or before the given date

What it means

AuditLog.getChangesetId(Instant) resolves the changeset in effect at a timestamp via select max(changesetId) where changelog.timestamp <= :ts. If no changeset was committed at or before that instant - or the changelog table is empty - the aggregate returns null and AuditException('No changeset exists at or before the given date') is thrown.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/audit/internal/AuditLogImpl.java:403

		for ( var row : results ) {
			map.put( row[0], changelogClass.cast( row[1] ) );
		}
		return map;
	}

	// --- Helpers ---

	private Object resolveChangesetIdForTimestamp(Object timestampValue) {
		requireChangelog();
		final String hql = "select max(e." + changesetIdProperty + ")"
				+ " from " + changelogName + " e"
				+ " where e." + timestampProperty + " <= :ts";
		final var result = auditSession
				.createSelectionQuery( hql, Object.class )
				.setParameter( "ts", timestampValue )
				.getSingleResultOrNull();
		if ( result == null ) {
			throw new AuditException( "No changeset exists at or before the given date" );
		}
		return result;
	}

	/**
	 * Convert an {@link Instant} to match the changelog entity's
	 * timestamp field type.
	 */
	private Object resolveTimestampValue(Instant instant) {
		if ( timestampFieldType == Date.class ) {
			return Date.from( instant );
		}
		else if ( timestampFieldType == LocalDateTime.class ) {
			return LocalDateTime.ofInstant( instant, ZoneId.systemDefault() );
		}
		else if ( timestampFieldType == Instant.class ) {
			return instant;
		}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Query at an instant at/after the first changeset - find it first with 'select min(r.<timestampProperty>) from <ChangelogEntity> r'.
  2. If the audit schema is empty, skip the temporal lookup entirely.
  3. Check timezone handling: align changelog timestamp storage (Date/LocalDateTime/epoch millis) with the query instant's zone.

Example fix

// before - before the first changeset
Object csId = auditLog.getChangesetId(Instant.parse("2020-01-01T00:00:00Z")); // throws
// after - use an instant at/after the first changeset
Object csId = auditLog.getChangesetId(firstChangesetInstant.plusNanos(1));
Defensive patterns

Strategy: validation

Validate before calling

// guard: only ask for a changeset when one exists at/before the instant
boolean any = !em.createQuery("select 1 from RevEntity r where r.at <= :t", Integer.class)
    .setParameter("t", Date.from(instant))
    .setMaxResults(1)
    .getResultList().isEmpty();
Object csId = any ? auditLog.getChangesetId(instant) : null;

Try / catch

try {
    csId = auditLog.getChangesetId(instant);
}
catch (AuditException e) {
    if (e.getMessage() != null && e.getMessage().contains("No changeset exists at or before")) {
        csId = null; // nothing was audited yet at that point in time
    }
    else throw e;
}

Prevention

When it happens

Trigger: Querying at an instant earlier than the first changeset; querying an empty audit schema; timezone/precision skew (LocalDateTime changelog fields are interpreted with ZoneId.systemDefault()) turning an 'after' instant into 'before'.

Common situations: Boundary queries at application startup before any audit data exists; tests against fresh databases; application servers in a different timezone than the writer.

Related errors


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