apache/beam · error · IOException

GET request for %s returned null

Error message

GET request for %s returned null

What it means

HL7v2IO's fetchMessage performs a GET of an HL7v2 message by ID via the Healthcare API client. If the client returns a null Message model (i.e., the API produced no entity), the method wraps this in an IOException with the requested message ID so the failure surfaces through the normal IO error path (it also increments the failedMessageGets counter in the catch block).

Source

Thrown at sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/healthcare/HL7v2IO.java:570

    private final Counter failedMessageGets =
        Metrics.counter(HL7v2MessageClient.class, "failed-message-reads");
    private final Counter successfulHL7v2MessageGets =
        Metrics.counter(HL7v2MessageClient.class, "successful-hl7v2-message-gets");
    private static final Logger LOG = LoggerFactory.getLogger(HL7v2MessageClient.class);
    private final HealthcareApiClient client;

    /** Instantiates a new HL7v2MessageClient (HCLS API v1). */
    HL7v2MessageClient(HealthcareApiClient client) {
      this.client = client;
    }

    private HL7v2Message fetchMessage(String msgId)
        throws IOException, ParseException, IllegalArgumentException {
      try {
        com.google.api.services.healthcare.v1.model.Message msg = client.getHL7v2Message(msgId);
        if (msg == null) {
          throw new IOException(String.format("GET request for %s returned null", msgId));
        }
        this.successfulHL7v2MessageGets.inc();
        return HL7v2Message.fromModel(msg);
      } catch (Exception e) {
        failedMessageGets.inc();
        LOG.warn(
            "Error fetching HL7v2 message with ID {} writing to Dead Letter Queue. ", msgId, e);
        throw e;
      }
    }
  }

  /**
   * List HL7v2 messages in HL7v2 Stores with optional filter.
   *
   * <p>This transform is optimized for splitting of message.list calls for large batches of
   * historical data and assumes rather continuous stream of sendTimes.
   *

View on GitHub (pinned to 12126d8942)

Solutions

  1. Verify the message ID / resource name exists in the HL7v2 store (list messages or check the Cloud Console) before fetching.
  2. Retry the fetch — transient API issues can yield an empty response; the surrounding code already retries/DEAD-letters via the catch block.
  3. Check the HL7v2 store path (project/location/dataset/store) used by the IO is correct and the message wasn't deleted by a concurrent consumer.
  4. Handle the IOException in the caller's error-handling PCollection so the pipeline doesn't crash on single missing messages.

Example fix

// before
String msgId = notification.getMessageName();
HL7v2Message msg = io.fetchMessage(msgId); // IOException if deleted

// after
try {
  HL7v2Message msg = io.fetchMessage(msgId);
} catch (IOException e) {
  LOG.warn("HL7v2 message %s no longer available, skipping", msgId, e);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (msgId == null || !msgId.matches("projects/[^/]+/locations/[^/]+/datasets/[^/]+/hl7V2Stores/[^/]+/messages/.+")) {
  throw new IllegalArgumentException("Invalid HL7v2 message name: " + msgId);
}

Try / catch

try {
  HL7v2Message msg = fetchMessage(msgId);
} catch (IOException | ParseException e) {
  LOG.warn("GET for {} returned null/failed: {}", msgId, e.getMessage());
  // route to dead-letter or retry
}

Prevention

When it happens

Trigger: fetchMessage(msgId) is called and client.getHL7v2Message(msgId) returns null — the Healthcare API GET request completed without throwing but yielded no message object (message deleted concurrently, bad store path, or empty response body deserialized to null).

Common situations: Streaming pipelines reading from an HL7v2 store while messages are being purged/acknowledged by another process; msgId assembled from notification payloads that reference already-deleted messages; misconfigured project/dataset/store path that returns an empty body instead of 404.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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