apache/pulsar · error · RestException

messageId is null

Error message

messageId is null

What it means

The triggerOffload admin endpoint requires a MessageIdImpl request body identifying the ledger/entry to offload. When the body is missing or null the endpoint immediately returns HTTP 400 with 'messageId is null'.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/PersistentTopics.java:4037

            @ApiResponse(responseCode = "405", description = "Operation is not allowed on the persistent topic"),
            @ApiResponse(responseCode = "409", description = "Offload already running"),
            @ApiResponse(responseCode = "412", description = "Topic name is not valid"),
            @ApiResponse(responseCode = "500", description = "Internal server error"),
            @ApiResponse(responseCode = "503", description = "Failed to validate global cluster configuration")})
    public void triggerOffload(
            @Suspended final AsyncResponse asyncResponse,
            @Parameter(description = "Specify the tenant", required = true)
            @PathParam("tenant") String tenant,
            @Parameter(description = "Specify the namespace", required = true)
            @PathParam("namespace") String namespace,
            @Parameter(description = "Specify topic name", required = true)
            @PathParam("topic") @Encoded String encodedTopic,
            @Parameter(description = "Whether leader broker redirected this call to this broker. For internal use.")
            @QueryParam("authoritative") @DefaultValue("false") boolean authoritative,
                               MessageIdImpl messageId) {
        try {
            if (messageId == null) {
                throw new RestException(Response.Status.BAD_REQUEST, "messageId is null");
            }
            validateTopicName(tenant, namespace, encodedTopic);
            internalTriggerOffload(asyncResponse, authoritative, messageId);
        } catch (WebApplicationException wae) {
            asyncResponse.resume(wae);
        } catch (Exception e) {
            asyncResponse.resume(new RestException(e));
        }
    }

    @GET
    @Path("/{tenant}/{namespace}/{topic}/offload")
    @Operation(summary = "Offload a prefix of a topic to long term storage")
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Offload a prefix of a topic to long term storage",
                    content = @Content(schema = @Schema(implementation = OffloadProcessStatus.class))),

View on GitHub (pinned to 820761864e)

Solutions

  1. Send a JSON body like {"ledgerId":123,"entryId":4} (with partitionIndex if applicable)
  2. Use the admin client method admin.topics().triggerOffload(topic, messageId) which serializes correctly
  3. Check the request Content-Type is application/json

Example fix

// before
curl -X POST .../persistent/public/default/t/offload
// after
curl -X POST -H 'Content-Type: application/json' \
  -d '{"ledgerId":4711,"entryId":0}' .../persistent/public/default/t/offload
Defensive patterns

Strategy: validation

Validate before calling

if (messageId == null) throw new IllegalArgumentException("Provide a MessageId (ledgerId/entryId) to offload");

Type guard

boolean isOffloadable(MessageId id) { return id instanceof MessageIdImpl && ((MessageIdImpl) id).getLedgerId() >= 0; }

Try / catch

try { admin.topics().triggerOffload(topic, messageId); } catch (PulsarAdminException e) { if (e.getStatusCode() == 400) { /* check request body */ } }

Prevention

When it happens

Trigger: POST /admin/v2/persistent/{tenant}/{namespace}/{topic}/offload with an empty body or JSON that does not deserialize into MessageIdImpl (e.g. wrong field names, non-JSON body).

Common situations: curl calls without -d payload; using MessageId instances of another implementation type that fail Jackson deserialization; client libs that serialize MessageId as a string instead of the ledgerId/entryId object.

Related errors


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