apache/flink · error · IOException
Cannot clean commit: File has trailing junk data.
Error message
Cannot clean commit: File has trailing junk data.
What it means
Thrown by LocalRecoverableFsDataOutputStream.commit() when the temp file's current length differs from the offset recorded in the recoverable. The 'clean commit' path requires the file to be exactly at the persisted offset; any extra bytes mean 'junk data' was appended after persist(), so the file is not in the expected state for an atomic rename.
Source
Thrown at flink-core/src/main/java/org/apache/flink/core/fs/local/LocalRecoverableFsDataOutputStream.java:156
static class LocalCommitter implements Committer {
private final LocalRecoverable recoverable;
LocalCommitter(LocalRecoverable recoverable) {
this.recoverable = checkNotNull(recoverable);
}
@Override
public void commit() throws IOException {
final File src = recoverable.tempFile();
final File dest = recoverable.targetFile();
// sanity check
if (src.length() != recoverable.offset()) {
// something was done to this file since the committer was created.
// this is not the "clean" case
throw new IOException("Cannot clean commit: File has trailing junk data.");
}
// rather than fall into default recovery, handle errors explicitly
// in order to improve error messages
try {
Files.move(src.toPath(), dest.toPath(), StandardCopyOption.ATOMIC_MOVE);
} catch (UnsupportedOperationException | AtomicMoveNotSupportedException e) {
if (!src.renameTo(dest)) {
throw new IOException(
"Committing file failed, could not rename " + src + " -> " + dest);
}
} catch (FileAlreadyExistsException e) {
throw new IOException(
"Committing file failed. Target file already exists: " + dest);
}
}
@OverrideView on GitHub (pinned to 2f3c205e92)
Solutions
- Stop writing to the stream immediately after persist() and before commit().
- Use commitAfterRecovery() when trailing data may exist; it truncates junk before moving.
- Ensure exactly one commit per recoverable and no post-persist writes.
- Audit the writer lifecycle so persist->commit is atomic with respect to writes.
Example fix
// before RecoverableWriter.CommitRecoverable rec = writer.persistForRecoverySafely(); out.write(extra); // appends junk committer.commit(); // throws // after RecoverableWriter.CommitRecoverable rec = writer.persistForRecoverySafely(); // no writes after persist committer.commit();
Defensive patterns
Strategy: validation
Validate before calling
void safeCommit(LocalRecoverable r) throws IOException {
if (r.tempFile().length() != r.offset())
throw new IOException("temp file changed after persist; use commitAfterRecovery()");
} Try / catch
try {
committer.commit();
} catch (IOException e) {
if (e.getMessage().contains("trailing junk data")) {
committer.commitAfterRecovery(); // truncates then moves
} else throw e;
} Prevention
- Cease all writes immediately after persist().
- Use commitAfterRecovery() whenever post-persist writes are possible.
- Enforce a single commit per recoverable.
When it happens
Trigger: Calling commit() on a LocalRecoverableFsDataOutputStream after data was written to the temp file beyond the persisted offset (e.g. writer kept writing between persist and commit).
Common situations: Race between persist() and continued writes; double-commit; a writer that did not stop writing after creating the committer; recovery scenarios where commit() is called instead of commitAfterRecovery() despite extra data.
Related errors
- Committing file failed, could not rename {} -> {}
- Committing file failed. Target file already exists: {}
- Directory {} does not exist or an I/O error occurred
- File Not Found: {}
- Missing data in tmp file: {}
AI-assisted analysis of apache/flink@2f3c205e92 (2026-08-14).
Data as JSON: /api/errors/0a4b8f59c71db74a.
Report an issue: GitHub.