apache/pulsar · error · IOException

Missing required permitMessages field for 'permit' command

Error message

Missing required permitMessages field for 'permit' command

What it means

ConsumerHandler rejects a WebSocket 'permit' command that omits the required permitMessages field. The proxy uses permitMessages to resume (or further throttle) message delivery in pull mode; without a numeric value it cannot update the pending-credit counter, so it fails the frame with an IOException, which closes the consumer session.

Source

Thrown at pulsar-websocket/src/main/java/org/apache/pulsar/websocket/ConsumerHandler.java:382

        MessageId originalMsgId = messageIdCache.asMap().remove(command.messageId);
        if (originalMsgId != null) {
            consumer.negativeAcknowledge(originalMsgId);
        } else {
            consumer.negativeAcknowledge(msgId);
        }
        checkResumeReceive();
    }

    private void handlePermit(ConsumerCommand command) throws IOException {
        log.debug()
                .attr("topic", consumer.getTopic())
                .attr("subscription", subscription)
                .attr("received", command.permitMessages)
                .attr("request", getSession().getRemoteSocketAddress())
                .log("/ ] Received permits request from");
        if (command.permitMessages == null) {
            throw new IOException("Missing required permitMessages field for 'permit' command");
        }
        if (this.pullMode) {
            int pending = pendingMessages.getAndAdd(-command.permitMessages);
            if (pending >= 0) {
                // Resume delivery
                receiveMessage();
            }
        }
    }

    @Override
    public void close() throws IOException {
        if (consumer != null) {
            if (!this.service.removeConsumer(this)) {
                log.warn().attr("topic", consumer.getTopic()).log("Failed to remove consumer handler");
            }
            consumer.closeAsync().thenAccept(x -> {
                log.debug().attr("topic", consumer.getTopic()).log("Closed consumer asynchronously");

View on GitHub (pinned to 820761864e)

Solutions

  1. Always include an integer permitMessages field in every 'permit' command frame
  2. Update the client SDK/library so permit frames are serialized with permitMessages set
  3. Validate the outbound JSON on the client before sending (assert permitMessages is a non-negative number)
  4. If stopping consumption entirely, close the consumer instead of sending a zero/null permit

Example fix

// before
{"type":"permit"}
// after
{"type":"permit","permitMessages":10}
Defensive patterns

Strategy: validation

Validate before calling

if (cmd == null || cmd.getPermitMessages() == null || cmd.getPermitMessages() < 0) { throw new IllegalArgumentException("permit command requires non-negative permitMessages"); }

Type guard

boolean hasPermitMessages(CommandPermit cmd) { return cmd != null && cmd.getPermitMessages() != null && cmd.getPermitMessages() >= 0; }

Try / catch

try { handler.handlePermit(cmd); } catch (IOException e) { log.error("permit frame rejected: {}", e.getMessage()); closeSession(); }

Prevention

When it happens

Trigger: A WebSocket client sends a JSON frame with command type 'permit' but no permitMessages property (or sends null). handlePermit parses the command and throws before touching pendingMessages.

Common situations: Hand-rolled client code emitting permit frames; client SDK version mismatch where the producer of the frame serializes a null permit; a proxy or script forwarding partially-populated commands.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


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