apache/beam · error · IllegalStateException

Received message missing ackId

Error message

Received message missing ackId

What it means

Each message returned by a Pub/Sub pull carries an ackId used to acknowledge/nack it. PubsubGrpcClient.pull throws IllegalStateException when the ReceivedMessage's ackId is empty, since without it the message can never be acknowledged and would be redelivered forever.

Source

Thrown at sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsub/PubsubGrpcClient.java:291

      Map<String, String> attributes = pubsubMessage.getAttributes();

      // Timestamp.
      long timestampMsSinceEpoch;
      if (timestampAttribute == null || timestampAttribute.isEmpty()) {
        if (!pubsubMessage.hasPublishTime()) {
          throw new IllegalStateException("Received message missing publishTime");
        }
        Timestamp timestampProto = pubsubMessage.getPublishTime();
        timestampMsSinceEpoch =
            timestampProto.getSeconds() * 1000 + timestampProto.getNanos() / 1000L / 1000L;
      } else {
        timestampMsSinceEpoch = extractTimestampAttribute(timestampAttribute, attributes);
      }

      // Ack id.
      String ackId = message.getAckId();
      if (ackId.isEmpty()) {
        throw new IllegalStateException("Received message missing ackId");
      }

      // Record id, if any.
      String recordId = null;
      if (idAttribute != null) {
        recordId = attributes.get(idAttribute);
      }
      if (recordId == null || recordId.isEmpty()) {
        // Fall back to the Pubsub provided message id.
        recordId = pubsubMessage.getMessageId();
      }

      incomingMessages.add(
          IncomingMessage.of(
              pubsubMessage, timestampMsSinceEpoch, requestTimeMsSinceEpoch, ackId, recordId));
    }
    return incomingMessages;
  }

View on GitHub (pinned to 12126d8942)

Solutions

  1. Fix the test mock/emulator to populate ackId on every ReceivedMessage
  2. Use the official Google Pub/Sub service or current client libraries
  3. Filter out invalid received messages before they reach Beam (proxy-side validation)
  4. Retry the pull; a transiently corrupted response is typically replaced by a valid one

Example fix

// mock before
ReceivedMessage.newBuilder().setMessage(msg).build();
// after
ReceivedMessage.newBuilder().setMessage(msg).setAckId("ack-123").build();
Defensive patterns

Strategy: try-catch

Validate before calling

if (receivedMessage.getAckId() == null || receivedMessage.getAckId().isEmpty()) {
  throw new IllegalStateException("ReceivedMessage has empty ackId");
}

Try / catch

try { messages = client.pull(subscription, deadline, batchSize, clock); } catch (IllegalStateException e) { log.error("Missing ackId: {}", e.getMessage()); }

Prevention

When it happens

Trigger: pull() processes a ReceivedMessage whose getAckId() returns an empty string while reading from a subscription via PubsubIO.read through the gRPC client.

Common situations: Mocked or synthetic subscription responses in tests missing ackId; broken proxy/emulator implementations; corrupted streaming-pull responses from a non-standard backend.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/7d3409be5e1fbf42. Report an issue: GitHub.