apache/beam · warning
Error fetching HL7v2 message with ID
Error message
Error fetching HL7v2 message with ID {} writing to Dead Letter Queue. What it means
HL7v2IO's fetchMessage DoFn retrieves each HL7v2 message by ID from the Healthcare API. On any exception during the GET (null response, API error, network failure), failedMessageGets is incremented and this warning logged with the message ID and cause; the exception is then rethrown so the pipeline's failure handling (e.g. HealthcareIOError wrapping via HL7v2IO.Read's error handling) decides the outcome.
Solutions
- Check the logged cause per msgId — 404 means the message was deleted/purged; 403 means IAM is missing.
- Grant the service account roles/healthcare.hl7V2MessageViewer on the store/dataset.
- Wrap the Read with error handling so failures go to a dead letter instead of failing the whole pipeline (HL7v2IO supports HealthcareIOError dead-letter on Read).
- Verify the hl7v2Store path format and that Pub/Sub notification IDs are still retained by the store's message retention config.
Example fix
// before
pipeline.apply(HL7v2IO.read().hl7V2Store(store)); // failures abort pipeline
// after
PCollection<HealthcareIOError<String>> dlq =
pipeline.apply(HL7v2IO.read().hl7V2Store(store)).get(HL7v2IO.Read.DEAD_LETTER); Defensive patterns
Strategy: try-catch
Validate before calling
// Java: validate store path before HL7v2IO.read()
if (!store.matches("projects/[^/]+/locations/[^/]+/datasets/[^/]+/hl7V2Stores/[^/]+")) {
throw new IllegalArgumentException("Invalid HL7v2 store: " + store);
} Try / catch
// Wrap per-message fetch and rethrow only after dead-lettering
try {
HL7v2Message msg = fetchMessage(msgId);
} catch (java.io.IOException e) {
failedMessageGets.inc();
context.output(DEAD_LETTER, HealthcareIOError.of(msgId, e));
throw e;
} Prevention
- Grant roles/healthcare.hl7V2MessageViewer to the pipeline service account.
- Consume HL7v2IO.Read dead-letter output.
- Check message retention so Pub/Sub-notified messages still exist at fetch time.
- Retry transient 5xx before treating a fetch as permanent.
When it happens
Trigger: Reading HL7v2IO.read().hl7V2Store(store) with message IDs that don't exist or were deleted; missing roles/healthcare.hl7V2MessageViewer permission; malformed store path; transient Healthcare API 5xx/timeout.
Common situations: Notifications from Pub/Sub referencing messages already purged; IAM changes mid-run; store name typos; quota exhaustion causing GET failures.
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
- Error fetching Fhir resource with ID
- Earliest message in has null or empty sendTime defaulting…
- Error executing GetPatientEverything: FHIR resources…
- Error search FHIR resources writing to Dead Letter Queue.
- Failed to ingest message
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/22ac6ea3a6182881.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/healthcare/HL7v2IO.java:576
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.
*
* <p>Note on Benchmarking: The default initial splitting on day will make more queries than
* necessary when used with very small data sets (or very sparse data sets in the sendTime
* dimension). If you are looking to get an accurate benchmark be sure to use sufficient volume of
* data with messages that span sendTimes over a realistic time range (days)
*
* <p>Implementation includes overhead for:View on GitHub (pinned to 12126d8942)