apache/pulsar · error · BookieException.BookieIllegalOpException

Invalid version type, expected it to be LongVersion

Error message

Invalid version type, expected it to be LongVersion

What it means

PulsarRegistrationManager.writeCookie implements BookKeeper's RegistrationManager contract using Pulsar's MetadataStore, which uses optimistic-concurrency Long versions (like ZooKeeper zxid-based versions). Before writing a cookie it must convert the Version on the cookie's Versioned payload to a LongVersion; if the caller passes a cookie whose version is neither Version.NEW nor a LongVersion instance, the manager cannot map it to a store version and throws BookieException.BookieIllegalOpException. This typically means the Versioned cookie object was constructed with an incompatible Version implementation (e.g. a version read from a different metadata backend or a hand-built/stale Versioned object).

Source

Thrown at pulsar-metadata/src/main/java/org/apache/pulsar/metadata/bookkeeper/PulsarRegistrationManager.java:231

            throw new BookieException.MetadataStoreException(e);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error().attr("bookieId", bookieId).exception(e)
                    .log("InterruptedException while checking registration ephemeral nodes");
            throw new BookieException.MetadataStoreException(e);
        }
    }

    @Override
    public void writeCookie(BookieId bookieId, Versioned<byte[]> cookieData) throws BookieException {
        String path = this.cookiePath + "/" + bookieId;
        try {
            long version;
            if (Version.NEW == cookieData.getVersion()) {
                version = -1L;
            } else {
                if (!(cookieData.getVersion() instanceof LongVersion)) {
                    throw new BookieException.BookieIllegalOpException(
                            "Invalid version type, expected it to be LongVersion");
                }
                version = ((LongVersion) cookieData.getVersion()).getLongVersion();
            }

            store.put(path, cookieData.getValue(), Optional.of(version))
                    .get(BLOCKING_CALL_TIMEOUT, MILLISECONDS);
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
            throw new BookieException.MetadataStoreException("Interrupted writing cookie for bookie " + bookieId, ie);
        } catch (ExecutionException e) {
            if (e.getCause() instanceof MetadataStoreException.BadVersionException) {
                throw new BookieException.CookieExistException(bookieId.toString());
            } else {
                throw new BookieException.MetadataStoreException("Failed to write cookie for bookie " + bookieId);
            }
        } catch (TimeoutException ex) {
            throw new BookieException.MetadataStoreException("Failed to write cookie for bookie " + bookieId, ex);

View on GitHub (pinned to 820761864e)

Solutions

  1. Obtain the cookie via this manager's readCookie(bookieId), which always returns a LongVersion version; use its version when writing.
  2. If the cookie is new, pass Versioned.value(cookieBytes) so getVersion() is Version.NEW and the write uses create semantics (version -1).
  3. Wrap any externally obtained version in new LongVersion(((AbstractVersion) v).getLongVersion())-style conversion, or re-read the cookie from the store to get the correct LongVersion before writing.
  4. Audit any custom code that constructs Versioned<byte[]> cookies and ensure it does not attach foreign Version implementations (e.g. zk Stat versions).

Example fix

// before
Versioned<byte[]> cookie = new Versioned<>(data, someZkBasedVersion);
regManager.writeCookie(bookieId, cookie);

// after
Optional<GetResult> existing = store.get(cookiePath + "/" + bookieId).get();
Version version = existing.isPresent()
        ? new LongVersion(existing.get().getStat().getVersion())
        : Version.NEW;
regManager.writeCookie(bookieId, new Versioned<>(data, version));
Defensive patterns

Strategy: validation

Validate before calling

Version v = cookieData.getVersion();
if (!(Version.NEW == v || v instanceof LongVersion)) {
    throw new IllegalArgumentException(
        "Cookie version must be Version.NEW or LongVersion, got: " + v.getClass().getName());
}

Type guard

static boolean isWritableCookieVersion(Versioned<byte[]> cookie) {
    return cookie != null
        && (Version.NEW == cookie.getVersion()
            || cookie.getVersion() instanceof LongVersion);
}

Try / catch

try {
    regManager.writeCookie(bookieId, cookie);
} catch (BookieException.BookieIllegalOpException e) {
    // version object came from another backend; re-read cookie to get LongVersion
    Versioned<byte[]> fresh = regManager.readCookie(bookieId);
    regManager.writeCookie(bookieId, new Versioned<>(cookie.getValue(), fresh.getVersion()));
}

Prevention

When it happens

Trigger: Calling writeCookie(bookieId, cookieData) where cookieData.getVersion() is not Version.NEW and not an instance of org.apache.pulsar.metadata.api.LongVersion — e.g. passing a Versioned<byte[]> whose version came from another RegistrationManager implementation, or one constructed programmatically with a custom/null Version rather than obtained from readCookie() on this manager.

Common situations: Mixing metadata backends: a cookie read under ZooKeeper-based BookKeeper metadata and replayed against a Pulsar MetadataStore-backed manager; a bookie's cookie cache carrying stale Version objects after re-registering against a different metadata service; custom bookie startup/fencing code that fabricates a Versioned cookie instead of calling readCookie; tests building Versioned<byte[]> with mock Version implementations.

Related errors


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