apache/pulsar · error · RestException

Metadata store is not configured for migration. Please ensur

Error message

Metadata store is not configured for migration. Please ensure you're using a supported source metadata store (e.g., ZooKeeper).

What it means

A 400 BAD_REQUEST thrown when the broker's local metadata store is not a DualMetadataStore, i.e. the broker was not started with a migration-capable (dual/source-wrapped) metadata store configuration. Migration can only be orchestrated through a DualMetadataStore that fronts both source and target stores.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/impl/MetadataMigrationBase.java:98

            @ApiResponse(responseCode = "204", description = "Migration started successfully"),
            @ApiResponse(responseCode = "400", description = "Invalid target URL"),
            @ApiResponse(responseCode = "409", description = "Migration already in progress"),
            @ApiResponse(responseCode = "500", description = "Internal server error")
    })
    public void startMigration(
            @Parameter(description = "Target metadata store URL", required = true)
            @QueryParam("target")
            String targetUrl) {
        validateSuperUserAccess();

        if (targetUrl == null || targetUrl.trim().isEmpty()) {
            throw new RestException(Response.Status.BAD_REQUEST, "Target URL is required");
        }

        try {
            // Check if metadata store is wrapped with DualMetadataStore
            if (!(pulsar().getLocalMetadataStore() instanceof DualMetadataStore dualStore)) {
                throw new RestException(Response.Status.BAD_REQUEST, "Metadata store is not configured for migration. "
                        + "Please ensure you're using a supported source metadata store (e.g., ZooKeeper).");
            }

            // Reject the request if a migration is already in progress or was completed. The migration
            // flag is always kept in the source store, so read it from there: after a completed
            // migration the dual store would route the read to the target store.
            var existingFlag = dualStore.getSourceStore().get(MigrationState.MIGRATION_FLAG_PATH).get();
            if (existingFlag.isPresent()) {
                MigrationState currentState = ObjectMapperFactory.getMapper().reader()
                        .readValue(existingFlag.get().getValue(), MigrationState.class);
                switch (currentState.getPhase()) {
                    case PREPARATION, COPYING -> throw new RestException(Response.Status.CONFLICT,
                            "Migration is already in progress (phase: " + currentState.getPhase() + ")");
                    case COMPLETED -> throw new RestException(Response.Status.CONFLICT,
                            "Migration has already been completed");
                    default -> {
                        // NOT_STARTED or FAILED: ok to start (or retry) the migration
                    }

View on GitHub (pinned to 820761864e)

Solutions

  1. Reconfigure the broker to use the dual metadata store wrapper so pulsar().getLocalMetadataStore() instanceof DualMetadataStore, then restart.
  2. Target the request at a broker that is actually configured for migration.
  3. Verify with broker logs/config which metadata store implementation is active at startup.

Example fix

# before: default store only
metadataStoreUrl=zookeeper:zk1:2181
# after: configure migration/dual store mode per migration docs so the broker
# instantiates DualMetadataStore wrapping ZooKeeper (source) and target store
# then restart brokers before calling the migration endpoint
Defensive patterns

Strategy: validation

Validate before calling

// ensure brokers run the dual metadata store before calling migration APIs
// (no public API check; verify config at deploy time)
assert brokerConf.metadataStoreConfiguredForMigration() : "configure DualMetadataStore first";

Try / catch

try { startMigration(target); }
catch (PulsarAdminException e) {
  if (e.getStatusCode() == 400 && e.getMessage().contains("not configured for migration")) {
    /* reconfigure broker with DualMetadataStore and restart */
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the migration start endpoint on a broker whose configurationMetadataStore/localMetadataStore is a plain store (e.g. default ZooKeeper store, not wrapped in DualMetadataStore via the migration configuration).

Common situations: Running migration admin commands against a normal (non-migration) broker deployment; forgetting to enable the dual metadata store wrapper in broker.conf (metadataStoreUrl / metadataStore config for migration mode); hitting the wrong broker cluster.

Related errors


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