{"record":{"id":"4bceabccf717256c","repo":"apache/iceberg","slug":"failed-to-recover-object","errorCode":null,"errorMessage":"Failed to recover object {}","messagePattern":"Failed to recover object (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"aws/src/main/java/org/apache/iceberg/aws/s3/S3FileIO.java","lineNumber":607,"sourceCode":"      return true;\n    }\n\n    LOG.info(\"Attempting to recover object {}\", version.key());\n    try {\n      // Perform a copy instead of deleting the delete marker\n      // so that recovery does not rely on delete permissions\n      client\n          .s3()\n          .copyObject(\n              builder ->\n                  builder\n                      .sourceBucket(bucket)\n                      .sourceKey(version.key())\n                      .sourceVersionId(version.versionId())\n                      .destinationBucket(bucket)\n                      .destinationKey(version.key()));\n    } catch (SdkException e) {\n      LOG.warn(\"Failed to recover object {}\", version.key(), e);\n      return false;\n    }\n\n    return true;\n  }\n\n  @Override\n  public void setCredentials(List<StorageCredential> credentials) {\n    Preconditions.checkArgument(credentials != null, \"Invalid storage credentials: null\");\n    // stop any refresh that might be scheduled\n    if (refreshFuture != null) {\n      refreshFuture.cancel(true);\n    }\n\n    // copy credentials into a modifiable collection for Kryo serde\n    this.storageCredentials = Lists.newArrayList(credentials);\n\n    // if the clients are already initialized, we need to close and allow them to be recreated","sourceCodeStart":589,"sourceCodeEnd":625,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/aws/src/main/java/org/apache/iceberg/aws/s3/S3FileIO.java#L589-L625","documentation":"S3FileIO.recoverObject() restores an object version (e.g. from a versioning-enabled bucket) by copying a prior version back to the key. If the AWS SDK copy raises SdkException, it logs this warning with the version key and returns false, signaling recovery failed for that object.","triggerScenarios":"recoverFile() called on a bucket without versioning enabled (no prior version to copy), or the caller lacks s3:GetObjectVersion/s3:PutObject permission, or the source version no longer exists (expired lifecycle).","commonSituations":"Attempted rollback of accidental deletes on buckets where versioning/safeguard feature was never enabled; lifecycle rules purging old versions; cross-account permission gaps.","solutions":["Enable bucket versioning before relying on recovery features","Verify IAM grants GetObjectVersion and PutObject on the bucket","Check the attached exception: NoSuchVersion means the version was expired by lifecycle policy","Confirm the version id exists via list-object-versions before recovering"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// confirm versioning is on and the version exists before recovery\nBucketVersioningConfiguration v = s3Client.getBucketVersioning(b -> b.bucket(bucket));\nif (!\"Enabled\".equals(v.getStatus())) throw new IllegalStateException(\"versioning disabled\");","typeGuard":null,"tryCatchPattern":"boolean ok = io.recoverFile(path); if (!ok) { /* object not recovered; inspect S3FileIO logs for 'Failed to recover object' */ }","preventionTips":["Enable S3 bucket versioning wherever recovery/rollback matters","Avoid lifecycle rules expiring versions shorter than your recovery window","Grant GetObjectVersion and PutObject to the recovery identity"],"tags":["aws","s3","versioning","recovery"],"backgroundTag":"resource-not-found","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}