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
- Query at an instant at/after the first changeset - find it first with 'select min(r.<timestampProperty>) from <ChangelogEntity> r'.
- If the audit schema is empty, skip the temporal lookup entirely.
- 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
- Seed or check the first changeset timestamp before issuing historical point-in-time lookups
- Keep writers and readers on consistent timezone settings; LocalDateTime fields use the system zone
- Skip temporal queries against fresh or empty audit schemas
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
- Changeset does not exist: <changesetId>
- Audit graph mutation plan used with non-graph action queue
- Audit graph mutation plan used with non-graph action queue
- null key for collection: %s
- Entity '<entityName>' is not audited
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/eb617cba066b800c.
Report an issue: GitHub.