apache/iceberg · warning

Failed to delete object at path

Error message

Failed to delete object at path {}

What it means

After batch deleteObject calls complete, S3FileIO.deleteFiles() collects the keys that S3 reported as failed and logs one warning per failed path. This means the object was NOT deleted despite the batch call succeeding at the transport level. Deletion then continues and the total failure count is tracked.

Solutions

  1. Inspect the logged path and verify IAM allows s3:DeleteObject on that key's prefix
  2. Check for S3 Object Lock, legal hold, or retention settings on the object
  3. Confirm the path/bucket is correct — typos produce un-deletable keys
  4. Retry the deletion; transient errors may succeed on re-run
Defensive patterns

Strategy: validation

Validate before calling

// before deleteFiles, confirm keys exist and are deletable
HeadResponse h = s3Client.headObject(b -> b.bucket(bucket).key(key)); // check Object Lock / retention headers

Try / catch

try { io.deleteFiles(paths); } catch (RuntimeException e) { LOG.error("deleteFiles aborted", e); } // individual failures are warnings only

Prevention

When it happens

Trigger: deleteFiles() issued for keys that do not exist in some edge configurations, or S3 batch delete responses containing per-key errors (e.g. access denied, lifecycle lock, object held by Object Lock / legal hold).

Common situations: Objects under S3 Object Lock retention; IAM denies for specific prefixes; eventual-consistency edge cases in legacy setups; wrong bucket/prefix in file paths.

Related errors


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

Appendix: source

Thrown at aws/src/main/java/org/apache/iceberg/aws/s3/S3FileIO.java:257

      }

      // Delete the remainder
      for (Map.Entry<String, Collection<String>> bucketToObjectsEntry :
          bucketToObjects.asMap().entrySet()) {
        String bucket = bucketToObjectsEntry.getKey();
        Collection<String> keys = bucketToObjectsEntry.getValue();
        Future<List<String>> deletionTask =
            executorService()
                .submit(() -> deleteBatch(clientForStoragePath("s3://" + bucket), bucket, keys));
        deletionTasks.add(deletionTask);
      }

      int totalFailedDeletions = 0;

      for (Future<List<String>> deletionTask : deletionTasks) {
        try {
          List<String> failedDeletions = deletionTask.get();
          failedDeletions.forEach(path -> LOG.warn("Failed to delete object at path {}", path));
          totalFailedDeletions += failedDeletions.size();
        } catch (ExecutionException e) {
          LOG.warn("Caught unexpected exception during batch deletion: ", e.getCause());
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
          deletionTasks.stream().filter(task -> !task.isDone()).forEach(task -> task.cancel(true));
          throw new RuntimeException("Interrupted when waiting for deletions to complete", e);
        }
      }

      if (totalFailedDeletions > 0) {
        throw new BulkDeletionFailureException(totalFailedDeletions);
      }
    }
  }

  private void tagFileToDelete(PrefixedS3Client client, String path, Set<Tag> deleteTags)
      throws S3Exception {

View on GitHub (pinned to 86d9c8fc54)