Conversation
client/src/test/java/com/microsoft/durabletask/LargePayloadIntegrationTests.java
Fixed
Show fixed
Hide fixed
There was a problem hiding this comment.
Pull request overview
Adds large payload support to the Durable Task Java SDK by introducing an external payload store abstraction, wiring it into client/worker/runner execution paths, and providing an Azure Blob Storage-backed implementation for Azure Functions scenarios.
Changes:
- Introduces
PayloadStore,LargePayloadOptions, and internal helpers to externalize/resolve payloads in protobuf messages. - Updates worker/client/orchestration runner to resolve incoming tokens and externalize outgoing large payloads; adds orchestrator response chunking.
- Adds new
azure-blob-payloadsmodule withBlobPayloadStore+ options, and wires it into the Azure Functions middleware.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| settings.gradle | Registers the new :azure-blob-payloads Gradle module. |
| client/src/main/java/com/microsoft/durabletask/PayloadStore.java | Public interface for upload/download + token recognition. |
| client/src/main/java/com/microsoft/durabletask/LargePayloadOptions.java | Configures externalization thresholds and max externalized payload size. |
| client/src/main/java/com/microsoft/durabletask/PayloadHelper.java | Centralizes size checks + store delegation for externalize/resolve. |
| client/src/main/java/com/microsoft/durabletask/PayloadInterceptionHelper.java | Resolves/externalizes payload fields inside orchestration/activity protobufs. |
| client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorkerBuilder.java | Adds externalized payload configuration + response chunk-size configuration. |
| client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java | Resolves incoming tokens, externalizes outputs, announces capability, and chunks orchestrator responses. |
| client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcClientBuilder.java | Adds externalized payload configuration for the client. |
| client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcClient.java | Externalizes outgoing inputs/reasons and resolves tokens in returned metadata. |
| client/src/main/java/com/microsoft/durabletask/OrchestrationRunner.java | Adds overloads to run orchestrations with externalized payload support. |
| client/src/test/java/com/microsoft/durabletask/PayloadHelperTest.java | Unit tests for thresholding, max cap behavior, and resolve/externalize round trips. |
| client/src/test/java/com/microsoft/durabletask/PayloadInterceptionHelperTest.java | Unit tests verifying request/response interception resolves/externalizes expected fields. |
| client/src/test/java/com/microsoft/durabletask/LargePayloadOptionsTest.java | Unit tests for defaults and validation behavior. |
| client/src/test/java/com/microsoft/durabletask/OrchestrationRunnerPayloadTest.java | Tests runner load+run behavior with/without a store. |
| client/src/test/java/com/microsoft/durabletask/DurableTaskGrpcWorkerBuilderTest.java | Tests chunk-size validation and externalized payload builder configuration. |
| client/src/test/java/com/microsoft/durabletask/LargePayloadIntegrationTests.java | End-to-end integration tests for externalization + autochunk behavior. |
| client/build.gradle | Makes test Java executable selection Windows-aware. |
| azurefunctions/src/main/java/.../OrchestrationMiddleware.java | Initializes a blob-backed payload store from env vars and passes it to the runner. |
| azurefunctions/build.gradle | Adds dependency on the new :azure-blob-payloads module. |
| azure-blob-payloads/build.gradle | New module build + publish/sign/spotbugs configuration. |
| azure-blob-payloads/src/main/java/.../BlobPayloadStoreOptions.java | Config object for blob-based payload storage. |
| azure-blob-payloads/src/main/java/.../BlobPayloadStore.java | Azure Blob-backed PayloadStore implementation with optional gzip. |
| azure-blob-payloads/src/main/java/.../AzureBlobPayloadsExtensions.java | Convenience methods to configure builders with blob payload storage. |
| azure-blob-payloads/src/test/java/.../BlobPayloadStoreOptionsTest.java | Unit tests for options validation and defaults. |
| azure-blob-payloads/spotbugs-exclude.xml | SpotBugs exclusions for exposed Azure SDK client objects in options. |
...lob-payloads/src/main/java/com/microsoft/durabletask/azureblobpayloads/BlobPayloadStore.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java
Show resolved
Hide resolved
client/src/test/java/com/microsoft/durabletask/LargePayloadIntegrationTests.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java
Outdated
Show resolved
Hide resolved
YunchuWang
left a comment
There was a problem hiding this comment.
Code Review: Large Payload Support
Good overall design — the PayloadStore abstraction, PayloadHelper threshold logic, and PayloadInterceptionHelper protobuf interception are well-structured. The response chunking implementation is solid. However, I found several issues that need attention.
Summary of Findings
Critical:
OrchestrationMiddlewaresilently enables blob storage for ALL Azure Functions apps (falls back toAzureWebJobsStorage)OrchestrationMiddlewarehardcodesBlobPayloadStore— violates thePayloadStoreabstraction
Medium (Performance):
3. BlobPayloadStore.upload() makes 2 HTTP calls for compressed uploads (upload + setHttpHeaders)
4. BlobPayloadStore.download() makes 2 HTTP calls (downloadStream + getProperties)
5. PayloadHelper.maybeExternalize() allocates full UTF-8 byte array just to check size
Low / Design:
6. BlobPayloadStore.extractBlobName() ignores container name from token
7. PayloadStore interface has no delete() — externalized payloads accumulate forever
8. PayloadInterceptionHelper — field.isInitialized() is always true in proto3
9. BlobPayloadStore constructor makes network call (ensureContainerExists)
...va/com/microsoft/durabletask/azurefunctions/internal/middleware/OrchestrationMiddleware.java
Outdated
Show resolved
Hide resolved
...va/com/microsoft/durabletask/azurefunctions/internal/middleware/OrchestrationMiddleware.java
Outdated
Show resolved
Hide resolved
...lob-payloads/src/main/java/com/microsoft/durabletask/azureblobpayloads/BlobPayloadStore.java
Outdated
Show resolved
Hide resolved
...lob-payloads/src/main/java/com/microsoft/durabletask/azureblobpayloads/BlobPayloadStore.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/PayloadHelper.java
Outdated
Show resolved
Hide resolved
...lob-payloads/src/main/java/com/microsoft/durabletask/azureblobpayloads/BlobPayloadStore.java
Show resolved
Hide resolved
...lob-payloads/src/main/java/com/microsoft/durabletask/azureblobpayloads/BlobPayloadStore.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/PayloadInterceptionHelper.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java
Show resolved
Hide resolved
YunchuWang
left a comment
There was a problem hiding this comment.
LGTM. Thorough review of all 27 changed files. Well-architected large payload support with clean interface abstraction (PayloadStore/PayloadStoreProvider), comprehensive protobuf interception at all 4 boundary points (client, worker, orchestration runner, AzFunctions middleware), correct chunking algorithm, and strong test coverage (1859 lines including 853-line integration suite).
Key strengths:
- Clean separation: client module has zero blob storage deps; azure-blob-payloads provides one implementation via SPI
- Security: decompression bomb protection (20 MiB cap), container name validation preventing cross-container access, no SAS tokens in payload tokens
- Backward compatible: zero overhead when externalization is not configured
- Correct JDK 8 compat in production code
Minor non-blocking suggestions for follow-up:
- Consider adding TaskFailedEvent.failureDetails interception in resolveHistoryEvent for cross-SDK completeness
- Consider adding Content-Type: application/json header on blob uploads for better Azure Portal observability
- BlobPayloadStoreOptionsTest.multipleAuthMethods_throws test name is slightly misleading (actually tests endpoint-without-credential)
None of these are blockers.
Issue describing the changes in this PR
Adding large payload support in the Java SDK.
Add large payload support for Durable Task Java SDK
Adds the ability to externalize large payloads (900 KB – 10 MB) to Azure Blob Storage, preventing gRPC message size failures (~4 MB limit). Payloads below 900 KB continue inline with no behavior change. This feature is fully supported for standalone DTS (Durable Task Scheduler) applications. Azure Functions support requires a corresponding .NET extension update and will be added in another PR.
Usage (standalone DTS)
How it works
blob:v1:<container>:<blobName>)PayloadTooLargeExceptionCode breakdown
New module:
azure-blob-payloadsBlobPayloadStore—PayloadStoreimplementation that uploads/downloads payloads to Azure Blob Storage with gzip compressionBlobPayloadStoreOptions— Builder-pattern configuration (connection string, endpoint+credential, or pre-configuredBlobServiceClient; container name, blob prefix, compression toggle)BlobPayloadStoreProvider—PayloadStoreProviderSPI implementation for auto-discovery viaServiceLoaderwhenDURABLETASK_LARGE_PAYLOADS_CONNECTION_STRINGenv var is setAzureBlobPayloadsExtensions— Convenience methods to configure blob storage payloads on worker/client buildersChanges to
clientmodulePayloadStore— Interface for upload/download/token detection (extensible to non-blob implementations)PayloadStoreProvider— SPI interface forServiceLoader-based auto-discoveryPayloadHelper— Internal threshold-checking and store-delegation logic (null/empty guard, fast char-length check, UTF-8 byte-length check, max-cap rejection, upload)PayloadInterceptionHelper— Resolves tokens in all protobuf history event types (ExecutionStarted,TaskCompleted,TaskScheduled,SubOrchestrationInstanceCreated,EventRaised, etc.) and externalizes payloads in orchestrator response actions (ScheduleTaskAction,CreateSubOrchestrationAction,CompleteOrchestrationAction)LargePayloadOptions— Configurable thresholds (default 900 KB externalization, 10 MB max)PayloadTooLargeException— Thrown when payload exceeds maxDurableTaskGrpcWorkerBuilder.useExternalizedPayloads()— Configures worker withPayloadStoreDurableTaskGrpcClientBuilder.useExternalizedPayloads()— Configures client withPayloadStoreDurableTaskGrpcWorker— Resolves activity request payloads, externalizes activity response payloadsDurableTaskGrpcClient— Externalizes orchestration inputs/events, resolves instance metadata outputsOrchestrationRunner— Resolves incoming history event payloads, externalizes outgoing action payloadsChanges to
azurefunctionsmoduleOrchestrationMiddleware— Extended to interceptDurableActivityTrigger(resolves input tokens, externalizes large outputs) and uses a resolve-onlyPayloadStorewrapper for orchestrations (resolves history tokens but does not externalize action inputs, since the .NET extension dispatches those)Tests
PayloadHelperTest— Threshold logic, null/empty handling, UTF-8 byte countingPayloadInterceptionHelperTest— Protobuf history event resolution, action externalizationLargePayloadOptionsTest— Builder validationOrchestrationRunnerPayloadTest— End-to-endOrchestrationRunnerwithPayloadStoreDurableTaskGrpcWorkerBuilderTest— Builder configuration validationLargePayloadIntegrationTests— Integration tests against DTS emulator + AzuriteBlobPayloadStoreOptionsTest— Options builder validationPull request checklist
CHANGELOG.mdAdditional information
Additional PR information