{"record":{"id":"291c6953dac60465","repo":"apache/iceberg","slug":"cannot-commit-s-metadata-location-s-has-changed-291c69","errorCode":null,"errorMessage":"Cannot commit %s: metadata location %s has changed from %s","messagePattern":"Cannot commit (.+?): metadata location (.+?) has changed from (.+?)","errorType":"exception","errorClass":"CommitFailedException","httpStatus":null,"severity":"warning","filePath":"core/src/main/java/org/apache/iceberg/jdbc/JdbcViewOperations.java","lineNumber":153,"sourceCode":"    }\n  }\n\n  @Override\n  protected String viewName() {\n    return viewIdentifier.toString();\n  }\n\n  @Override\n  protected FileIO io() {\n    return fileIO;\n  }\n\n  private void validateMetadataLocation(Map<String, String> view, ViewMetadata base) {\n    String catalogMetadataLocation = view.get(JdbcTableOperations.METADATA_LOCATION_PROP);\n    String baseMetadataLocation = base != null ? base.metadataFileLocation() : null;\n\n    if (!Objects.equals(baseMetadataLocation, catalogMetadataLocation)) {\n      throw new CommitFailedException(\n          \"Cannot commit %s: metadata location %s has changed from %s\",\n          viewIdentifier, baseMetadataLocation, catalogMetadataLocation);\n    }\n  }\n\n  private void updateView(String newMetadataLocation, String oldMetadataLocation)\n      throws SQLException, InterruptedException {\n    int updatedRecords =\n        JdbcUtil.updateView(\n            connections, catalogName, viewIdentifier, newMetadataLocation, oldMetadataLocation);\n\n    if (updatedRecords == 1) {\n      LOG.debug(\"Successfully committed to existing view: {}\", viewIdentifier);\n    } else {\n      throw new CommitFailedException(\n          \"Failed to update view %s from catalog %s\", viewIdentifier, catalogName);\n    }\n  }","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/jdbc/JdbcViewOperations.java#L135-L171","documentation":"validateMetadataLocation performs optimistic-concurrency validation: before updating an existing view, the metadata location recorded in the JDBC catalog is compared to the location of the caller's base ViewMetadata. If they differ, a CommitFailedException is thrown because another committer changed the view since this operation's base was read.","triggerScenarios":"Calling JdbcViewOperations.commit() with a base ViewMetadata whose metadataFileLocation does not match JdbcTableOperations.METADATA_LOCATION_PROP currently stored in the catalog — i.e. a concurrent commit landed between refresh() and commit().","commonSituations":"Two engines (Spark + Trino) or two jobs writing the same view concurrently; a long-running operation holding a stale view object while someone else recreates the view; recreating the view (drop + create) while another process holds it open.","solutions":["Call viewOps.refresh() and re-apply the change on the fresh metadata, then retry the commit","Serialize writers to the same view (single writer per view, or external locking)","Investigate the concurrent writer — check which process committed between your read and commit","Catch CommitFailedException and retry with backoff, as it is designed to signal retryable conflicts"],"exampleFix":"// before: commit stale base\nViewMetadata base = loadOnce();\ncommit(view, base); // CommitFailedException\n// after: refresh and retry\ntry {\n  commit(view, base);\n} catch (CommitFailedException e) {\n  viewOps.refresh();\n  commit(applyChange(viewOps.current()), viewOps.current());\n}","handlingStrategy":"retry","validationCode":"// ensure your base is fresh right before commit\nviewOps.refresh();\nif (!Objects.equals(base.metadataFileLocation(),\n    viewOps.current().metadataFileLocation())) {\n  throw new IllegalStateException(\"Base is stale; rebase before commit\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  viewOps.commit(request);\n} catch (CommitFailedException e) {\n  viewOps.refresh();\n  retryCommitWithBackoff(); // re-apply change on fresh metadata\n}","preventionTips":["Always refresh() before committing long-lived view objects","Use a single writer per view or external locking when multiple engines write the same view","Catch CommitFailedException and retry with exponential backoff","Avoid drop+recreate of views that other processes hold open"],"tags":["optimistic-concurrency","commit-conflict","jdbc","catalog"],"backgroundTag":"invalid-state-transition","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}