apache/pulsar · warning · RestException

Target URL is required

Error message

Target URL is required

What it means

A 400 BAD_REQUEST thrown by the metadata migration admin endpoint (MetadataMigrationBase.startMigration) when the required 'target' query parameter is missing or blank. The migration framework needs the target metadata store URL (e.g. a RocksDB or Etcd/metastore URL) to copy metadata to.

Source

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

    }

    @POST
    @Path("/start")
    @Operation(summary = "Start metadata store migration")
    @ApiResponses(value = {
            @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,

View on GitHub (pinned to 820761864e)

Solutions

  1. Pass the target metadata store URL, e.g. ?target=rocksdb:/data/target-metadata.
  2. Ensure the parameter name is exactly 'target' and non-empty after trimming.
  3. URL-encode the target URL if it contains special characters like ? or &.

Example fix

// before
curl -X POST http://broker:8080/admin/v3/migration
// after
curl -X POST 'http://broker:8080/admin/v3/migration?target=rocksdb%3A%2Fdata%2Ftarget-metadata'
Defensive patterns

Strategy: validation

Validate before calling

if (!targetUrl || targetUrl.trim().isEmpty()) {
  throw new IllegalArgumentException("?target=<metadata store URL> is required");
}

Try / catch

try { startMigration(target); }
catch (PulsarAdminException e) {
  if (e.getStatusCode() == 400) { /* fix the query parameter and retry */ }
  else throw e;
}

Prevention

When it happens

Trigger: Calling POST/PUT on the metadata-migration admin path without the ?target= query parameter, or with target= (empty) or only whitespace.

Common situations: Copy/pasting the endpoint example without the query param; URL encoding issue dropping the value; forgetting the target URL after configuring only the source/dual store.

Understand the failure class

Background: Missing required parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.

Related errors


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