apache/pulsar · error · UnsupportedOperationException

BookKeeper client is not available

Error message

BookKeeper client is not available

What it means

getBookKeeperClient() throws UnsupportedOperationException when the broker's managed-ledger storage default class is not the BookKeeper-based implementation. The broker can be configured with alternative storage backends, and a raw BookKeeper client only exists for the default BookKeeper storage class, so the method refuses to return anything for other backends (see the TODO in the source).

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/PulsarService.java:1662

        return this.nsService;
    }

    public Optional<WorkerService> getWorkerServiceOpt() {
        return functionWorkerService;
    }

    public WorkerService getWorkerService() throws UnsupportedOperationException {
        return functionWorkerService.orElseThrow(() -> new UnsupportedOperationException("Pulsar Function Worker "
                + "is not enabled, probably functionsWorkerEnabled is set to false"));
    }

    public BookKeeper getBookKeeperClient() {
        ManagedLedgerStorageClass defaultStorageClass = getManagedLedgerStorage().getDefaultStorageClass();
        if (defaultStorageClass instanceof BookkeeperManagedLedgerStorageClass bkStorageClass) {
            return bkStorageClass.getBookKeeperClient();
        } else {
            // TODO: Refactor code to support other than default bookkeeper based storage class
            throw new UnsupportedOperationException("BookKeeper client is not available");
        }
    }

    public ManagedLedgerFactory getDefaultManagedLedgerFactory() {
        return getManagedLedgerStorage().getDefaultStorageClass().getManagedLedgerFactory();
    }

    /**
     * First, get <code>LedgerOffloader</code> from local map cache,
     * create new <code>LedgerOffloader</code> if not in cache or
     * the <code>OffloadPolicies</code> changed, return the <code>LedgerOffloader</code> directly if exist in cache
     * and the <code>OffloadPolicies</code> not changed.
     *
     * @param namespaceName NamespaceName
     * @param offloadPolicies the OffloadPolicies
     * @return LedgerOffloader
     */
    public LedgerOffloader getManagedLedgerOffloader(NamespaceName namespaceName, OffloadPoliciesImpl offloadPolicies) {

View on GitHub (pinned to 820761864e)

Solutions

  1. Ensure the broker uses the default BookKeeper-based managed ledger storage so getBookKeeperClient() returns a client.
  2. Guard your code: check getManagedLedgerStorage().getDefaultStorageClass() instanceof BookkeeperManagedLedgerStorageClass before calling the getter, and handle the alternative case.
  3. If you maintain the extension, track the upstream TODO to refactor the API for non-default storage classes rather than catching and working around it.
  4. Access BookKeeper indirectly via ledger APIs (e.g. through the managed ledger factory) where the backend type does not matter.

Example fix

// before
BookKeeper bk = pulsar.getBookKeeperClient();
// after
if (pulsar.getManagedLedgerStorage().getDefaultStorageClass() instanceof BookkeeperManagedLedgerStorageClass bkStorage) {
    BookKeeper bk = bkStorage.getBookKeeperClient();
} else {
    // fall back to backend-agnostic managed ledger APIs
}
Defensive patterns

Strategy: type-guard

Validate before calling

boolean bkAvailable = pulsar.getManagedLedgerStorage().getDefaultStorageClass()
        instanceof BookkeeperManagedLedgerStorageClass;
if (!bkAvailable) {
    // skip BookKeeper-specific code paths
}

Type guard

boolean isBookKeeperStorage(PulsarService pulsar) {
    return pulsar.getManagedLedgerStorage().getDefaultStorageClass()
        instanceof BookkeeperManagedLedgerStorageClass;
}

Try / catch

try {
    BookKeeper bk = pulsar.getBookKeeperClient();
    // use bk
} catch (UnsupportedOperationException e) {
    if ("BookKeeper client is not available".equals(e.getMessage())) {
        // non-BookKeeper storage backend: use managed-ledger APIs instead
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling pulsar.getBookKeeperClient() (commonly from protocol handlers, plugins, extensions, or tests) on a broker whose managed ledger storage default storage class is not BookkeeperManagedLedgerStorageClass.

Common situations: Custom/experimental storage backends or test harnesses that replace managed ledger storage; plugin code assuming the default BookKeeper backend; brokers running with alternative storage configured via configuration.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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