aeron-io/aeron · error · ArchiveException

invalid nextRecordingId: expected value greater or equal to…

Error message

invalid nextRecordingId: expected value greater or equal to " + (recordingId + 1) + ", was " + nextRecordingId

What it means

Thrown when the catalog's nextRecordingId field, persisted in the catalog file, is lower than recordingId + 1 for an existing entry — i.e. the catalog's high-water-mark of recording IDs is inconsistent with an entry it contains. This guards against corrupted or hand-edited catalog files where the next ID counter was reset while entries still exist, which would cause new recordings to reuse existing IDs. Only thrown when the catalog is writable.

Solutions

  1. Restore a consistent catalog: run ArchiveTool verify/recover against the archive directory to rebuild or repair the catalog
  2. Restore the correct catalog file from a backup that includes all recorded entries
  3. Repair nextRecordingId to recordingId + 1 using CatalogTool or ArchiveTool repair options if you accept the risk after verifying entries
  4. Open the catalog read-only (writable=false) if you only need to inspect recordings, which bypasses this check

Example fix

// before
cp backup/catalog catalog // older catalog overwritten newer one
// after
java -cp aeron-samples.jar io.aeron.archive.tool.ArchiveTool verify /archive/dir
# then restore catalog from a backup taken after the last recording, or repair nextRecordingId
Defensive patterns

Strategy: try-catch

Validate before calling

// Before opening writable, sanity check the catalog header via CatalogTool:
// java -cp aeron-samples.jar io.aeron.archive.tool.CatalogTool describe <archive-dir>

Try / catch

try (Catalog catalog = new Catalog(archiveDir, clock, fileSyncLevel, writable=true, capacity, null, null)) {
    // use catalog
} catch (ArchiveException e) {
    if (e.getMessage().startsWith("invalid nextRecordingId")) {
        // catalog header inconsistent with entries: run ArchiveTool verify / restore backup
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Opening a writable Catalog whose header nextRecordingId is 0-positive but < max(recordingId)+1, typically after a corrupted catalog, a partial write/crash during catalog update, manual truncation, or copying an older catalog file over a newer one.

Common situations: Disk corruption or crash without clean shutdown truncating catalog updates; restoring catalog from backup taken before newer recordings were made; mixing catalog files across archive instances; ArchiveTool usage that mutated entries without updating the header counter.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12). Data as JSON: /api/errors/b3a5f9ebcaa5956a. Report an issue: GitHub.

Appendix: source

Thrown at aeron-archive/src/main/java/io/aeron/archive/Catalog.java:976

            recordingId = recordingId(catalogBuffer);
            if (isValidDescriptor(catalogBuffer))
            {
                catalogIndex.add(recordingId, offset);
            }

            offset += frameLength;
        }

        nextRecordingDescriptorOffset = offset;

        if (0 == nextRecordingId)
        {
            nextRecordingId = recordingId + 1;
        }
        else if (writable && nextRecordingId < recordingId + 1)
        {
            throw new ArchiveException("invalid nextRecordingId: expected value greater or equal to " +
                (recordingId + 1) + ", was " + nextRecordingId);
        }
    }

    private void invokeEntryProcessor(final int recordingDescriptorOffset, final CatalogEntryProcessor consumer)
    {
        descriptorHeaderDecoder.wrap(
            catalogBuffer, 0, DESCRIPTOR_HEADER_LENGTH, RecordingDescriptorHeaderDecoder.SCHEMA_VERSION);

        descriptorHeaderEncoder.wrap(catalogBuffer, 0);

        descriptorDecoder.wrap(
            catalogBuffer,
            DESCRIPTOR_HEADER_LENGTH,
            RecordingDescriptorDecoder.BLOCK_LENGTH,
            RecordingDescriptorDecoder.SCHEMA_VERSION);

        descriptorEncoder.wrap(catalogBuffer, DESCRIPTOR_HEADER_LENGTH);

View on GitHub (pinned to 6d60124e15)