aeron-io/aeron · error · IOException
failed to read fragment header
Error message
failed to read fragment header
What it means
When opening a recording segment for replay, ReplaySession reads the first HEADER_LENGTH bytes of the segment file and throws this IOException if the read returns fewer bytes than a full fragment header. This means the segment file is missing data at the expected offset, so the replay cannot proceed safely — the file is truncated or the offset is beyond the written data.
Solutions
- Verify the segment file size on disk; if it is truncated or corrupt, restore it from a backup or re-record the data
- If replaying a live recording, request a position within the recorded data (e.g. from startPosition) rather than the very end, or retry after more data is written
- Check that the archive's segmentFileLength configuration matches the value used when the recording was created
- Catch IOException in the replay consumer and re-subscribe to the replay or fall back to the last known-good position
Example fix
// before: replay immediately at the live end of an active recording long pos = recordingPositionCounter.get(); archive.startReplay(recordingId, pos, Long.MAX_VALUE); // after: replay only fully written data, e.g. joined position long pos = joinedPosition; // or wait until recordingPosition > requested pos archive.startReplay(recordingId, Math.min(pos, recordingPositionCounter.get()), len);
Defensive patterns
Strategy: retry
Validate before calling
File seg = new File(archiveDir, segmentFileName);
if (!seg.exists() || seg.length() < HEADER_LENGTH) {
throw new IllegalStateException("segment too short/truncated: " + seg);
} Type guard
static boolean segmentIsReadable(FileChannel ch, long segmentOffset) throws IOException {
return ch.size() - segmentOffset >= HEADER_LENGTH;
} Try / catch
try {
subscription = archive.startReplay(recordingId, position, length, channel, streamId);
} catch (IOException e) {
if (e.getMessage().contains("failed to read fragment header")) {
// truncated/partially written segment: retry from a safe position after recording progresses
long safePos = recordingPositionCounter.get() - segmentLength;
retryReplay(recordingId, Math.max(safePos, startPosition));
} else { throw e; }
} Prevention
- Avoid replaying a live recording from the extreme end where segments may be only partially written
- Shut archives down cleanly so segment files are fully flushed; treat post-crash segments as suspect
- Keep segmentFileLength consistent between recording and replay configurations
- Only copy/restore archive directories while the archive is quiesced
When it happens
Trigger: notHeaderAligned (called during init when positioning the replay) issues channel.read(byteBuffer, segmentOffset) on the segment file channel and the read returns less than HEADER_LENGTH. Happens when segmentOffset points at or past the end of a short/truncated segment file, or when the replay position was advanced into a region that was never written (e.g. live replay racing an active recording, or a partially flushed segment).
Common situations: Replaying a recording whose last segment was truncated by a crash or unclean shutdown; replaying a live recording from the very end where the segment has only a partial header written; replaying from a copied/mirrored archive directory that was copied mid-write; wrong segment length configuration so offsets land outside the file.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- failed to read link file=" + linkFile
- invalid position: " + position
- invalid length: " + length
- length must be positive
- fromPosition + " position not aligned to valid fragment
AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12).
Data as JSON: /api/errors/aa7945d2a73ca9a3.
Report an issue: GitHub.
Appendix: source
Thrown at aeron-archive/src/main/java/io/aeron/archive/ReplaySession.java:630
}
}
fileChannel = FileChannel.open(segmentFile.toPath(), FILE_OPTIONS);
}
static boolean notHeaderAligned(
final FileChannel channel,
final UnsafeBuffer buffer,
final int segmentOffset,
final int termOffset,
final int termId,
final int streamId) throws IOException
{
final ByteBuffer byteBuffer = buffer.byteBuffer();
byteBuffer.clear().limit(HEADER_LENGTH);
if (HEADER_LENGTH != channel.read(byteBuffer, segmentOffset))
{
throw new IOException("failed to read fragment header");
}
return isInvalidHeader(buffer, streamId, termId, termOffset);
}
private void state(final State newState, final String reason)
{
logStateChange(state, newState, sessionId, recordingId, replayPosition, null == reason ? "" : reason);
state = newState;
}
@SuppressWarnings("unused")
private void logStateChange(
final State oldState,
final State newState,
final long sessionId,
final long recordingId,
final long position,View on GitHub (pinned to 6d60124e15)