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);
            }
        }

        @Override

View on GitHub (pinned to 2f3c205e92)

Solutions

  1. Stop writing to the stream immediately after persist() and before commit().
  2. Use commitAfterRecovery() when trailing data may exist; it truncates junk before moving.
  3. Ensure exactly one commit per recoverable and no post-persist writes.
  4. 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

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


AI-assisted analysis of apache/flink@2f3c205e92 (2026-08-14). Data as JSON: /api/errors/0a4b8f59c71db74a. Report an issue: GitHub.