apache/pulsar · error · ConflictException

Failed to update from old:%s to value:%s

Error message

Failed to update from old:%s to value:%s

What it means

ConflictException thrown by MetadataStoreTableViewImpl.put when the configured conflictResolver rejects the update: the resolver test(oldValue, newValue) returned false, so the read-modify-update on the cached entry is aborted with this formatted message showing old and new values.

Source

Thrown at pulsar-metadata/src/main/java/org/apache/pulsar/metadata/tableview/impl/MetadataStoreTableViewImpl.java:412

    private String getKey(String path) {
        return path.replaceFirst(pathPrefix + "/", "");
    }

    public boolean exists(String key) {
        return immutableData.containsKey(key);
    }

    public T get(String key) {
        return data.get(key);
    }

    public CompletableFuture<Void> put(String key, T value) {
        String path = getPath(key);
        return cache.readModifyUpdateOrCreate(path, (old) -> {
            if (conflictResolver.test(old.orElse(null), value)) {
                return value;
            } else {
                throw new ConflictException(
                        String.format("Failed to update from old:%s to value:%s", old, value));
            }
        }).thenCompose(__ -> doHandleNotification(path)) // immediately notify local tableview
        .exceptionally(e -> {
            if (e.getCause() instanceof MetadataStoreException.BadVersionException) {
                throw FutureUtil.wrapToCompletionException(new ConflictException(
                        String.format("Failed to update to value:%s", value)));
            }

            throw FutureUtil.wrapToCompletionException(e.getCause());
        });
    }

    public CompletableFuture<Void> delete(String key) {
        String path = getPath(key);
        return cache.delete(path)
                .thenCompose(__ -> doHandleNotification(path)); // immediately notify local tableview
    }

View on GitHub (pinned to 820761864e)

Solutions

  1. Make the value pass the conflictResolver you configured, or adjust the resolver logic to accept the transition.
  2. Check the old value in the message to see why the transition was rejected (e.g. version/ordering regression).
  3. If the write is legitimate after a restart or state reset, delete the key first or seed the resolver with correct baseline state.
  4. Handle ConflictException from the returned CompletableFuture and re-read the current tableview state before retrying.

Example fix

// before
.resolveConflict((old, next) -> next >= old)  // rejects stale lower values
// after
.resolveConflict((old, next) -> next >= old || isReset(next))
Defensive patterns

Strategy: try-catch

Try / catch

tableView.put(key, value).exceptionally(ex -> {
    Throwable c = FutureUtil.unwrapCompletionException(ex);
    if (c instanceof ConflictException) {
        // re-read state via tableview.get(key), reconcile, retry
    }
    throw ex instanceof RuntimeException ? (RuntimeException) ex : new RuntimeException(ex);
});

Prevention

When it happens

Trigger: Calling tableview.put(key, value) where the existing value (or null when absent) fails the ConflictResolver predicate supplied at tableview construction, e.g. a resolver that requires monotonically increasing values receives an older value.

Common situations: Out-of-order writes from a producer applying stale state, concurrent writers fighting over the same key with a last-writer-loses-resolver rejecting, or putting a value that violates domain rules encoded in the resolver.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/fa1abe19164993c7. Report an issue: GitHub.