apache/iceberg · error · IllegalArgumentException

Cannot write delete files in a v1 table

Error message

Cannot write delete files in a v1 table

What it means

Delete files (position/equality deletes) require format version 2 or later; writeDeleteManifest explicitly throws IllegalArgumentException when asked to write a delete manifest for a v1 table since v1 has no delete-file support.

Source

Thrown at core/src/main/java/org/apache/iceberg/ManifestFiles.java:379

  /**
   * Create a new {@link ManifestWriter} for the given format version with custom writer properties.
   *
   * @param formatVersion a target format version
   * @param spec a {@link PartitionSpec}
   * @param outputFile an {@link EncryptedOutputFile} where the manifest will be written
   * @param snapshotId a snapshot ID for the manifest entries, or null for an inherited ID
   * @param writerProperties properties passed through to the underlying file writer
   * @return a manifest writer
   */
  public static ManifestWriter<DeleteFile> writeDeleteManifest(
      int formatVersion,
      PartitionSpec spec,
      EncryptedOutputFile outputFile,
      Long snapshotId,
      Map<String, String> writerProperties) {
    switch (formatVersion) {
      case 1:
        throw new IllegalArgumentException("Cannot write delete files in a v1 table");
      case 2:
        return new ManifestWriter.V2DeleteWriter(spec, outputFile, snapshotId, writerProperties);
      case 3:
        return new ManifestWriter.V3DeleteWriter(spec, outputFile, snapshotId, writerProperties);
      case 4:
        return new ManifestWriter.V4DeleteWriter(spec, outputFile, snapshotId, writerProperties);
    }
    throw new UnsupportedOperationException(
        "Cannot write manifest for table version: " + formatVersion);
  }

  /**
   * Encode the {@link ManifestFile} to a byte array by using avro encoder.
   *
   * @param manifestFile a {@link ManifestFile}, which should always be a {@link
   *     GenericManifestFile}.
   * @return the binary data.
   * @throws IOException if encounter any IO error when encoding.

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Migrate the table to format version 2 (rewrite table metadata with format-version=2) before writing delete files
  2. Use overwrite/rewrite-based delete semantics for v1 tables instead of delete files
  3. Verify the table's format-version property before running delete jobs

Example fix

// before: writing delete manifest against a v1 table
ManifestFiles.writeDeleteManifest(1, spec, outputFile, snapshotId, props);
// after: migrate table to v2 first, then
ManifestFiles.writeDeleteManifest(2, spec, outputFile, snapshotId, props);
Defensive patterns

Strategy: validation

Validate before calling

if (table.ops().current().formatVersion() < 2) {
  throw new IllegalStateException("Delete files require format version >= 2; migrate the table first");
}

Try / catch

try {
  ManifestFiles.writeDeleteManifest(formatVersion, spec, out, snapshotId, props);
} catch (IllegalArgumentException e) {
  if (e.getMessage().contains("v1 table")) { /* switch to overwrite semantics or migrate */ }
  else throw e;
}

Prevention

When it happens

Trigger: Calling ManifestFiles.writeDeleteManifest with formatVersion 1, or performing a delete/copy-on-write operation against a table whose format-version is 1.

Common situations: Trying row-level deletes or MERGE operations on legacy v1 tables; migrating writers but not the table format; misconfigured table creation defaulting to v1.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/d9eae2648fd2e000. Report an issue: GitHub.