Skip to content

Fix entity locking deserialization and add Jackson support for EntityInstanceId/EntityMetadata#281

Merged
bachuv merged 4 commits intomainfrom
vabachu/entity-lock-serialization
Apr 22, 2026
Merged

Fix entity locking deserialization and add Jackson support for EntityInstanceId/EntityMetadata#281
bachuv merged 4 commits intomainfrom
vabachu/entity-lock-serialization

Conversation

@bachuv
Copy link
Copy Markdown
Contributor

@bachuv bachuv commented Apr 20, 2026

Issue describing the changes in this PR

Fixes three serialization/deserialization bugs affecting durable entities in the Azure Functions code path and adds .NET parity for EntityInstanceId JSON handling.

1. Entity locking fails with deserialization error in Azure Functions

Bug: When an orchestration calls ctx.lockEntities() in the Azure Functions code path, the lock grant arrives as an EventRaised history event. During replay, handleEventRaised() attempts to deserialize the event payload as AutoCloseable.class via Jackson. Since AutoCloseable is an interface, Jackson throws:

Cannot construct instance of `java.lang.AutoCloseable` (no Creators, like default constructor, exist): 
abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information

This caused any orchestration using lockEntities() to fail on replay in Azure Functions. The DTS/emulator path was unaffected because lock grants arrive as proto EntityLockGranted events there, handled by a separate code path.

Fix: Added a branch in handleEventRaised() that checks matchingTaskRecord.getDataType() == AutoCloseable.class. When detected, it skips Jackson deserialization and instead sets the critical section state (isInCriticalSection, lockedEntityIds) and completes the task with null — mirroring what handleEntityLockGranted() does for the proto path. The null value flows into the thenApply lambda in lockEntities(), which ignores it and returns the AutoCloseable unlock handle.

Files changed: TaskOrchestrationExecutor.java
Tests added: lockEntities_lockGrantedViaEventRaised_succeeds in TaskOrchestrationEntityEventTest.java


2. EntityInstanceId fails Jackson deserialization when embedded in orchestration payloads

Bug: EntityInstanceId had no Jackson annotations, no default constructor, and no setters — only an immutable two-arg constructor. When users embed an EntityInstanceId in an orchestration input POJO (e.g., CounterPayload), Jackson cannot deserialize it during orchestration replay. The .NET SDK avoids this by registering a custom JsonConverter that serializes EntityInstanceId as a compact "@name@key" string.

Fix: Added @JsonSerialize and @JsonDeserialize annotations to EntityInstanceId with inner Serializer/Deserializer classes. These serialize to "@name@key" and deserialize via EntityInstanceId.fromString(), matching the .NET SDK's compact string representation.

Files changed: EntityInstanceId.java
Tests added: jacksonSerialization_* and jacksonDeserialization_* tests in EntityInstanceIdTest.java


3. TypedEntityMetadata cannot be serialized in Azure Functions HTTP responses

Bug: The Azure Functions Java worker uses Gson to serialize HTTP response bodies. When samples pass a TypedEntityMetadata<T> object directly to .body(entity), Gson hits internal fields it cannot handle:

  • Class<T> stateTypeUnsupportedOperationException: Attempted to serialize java.lang.Class
  • DataConverter dataConverter → not serializable

Fix (SDK): Added Jackson annotations to EntityMetadata and TypedEntityMetadata:

  • @JsonIgnore on serializedState, dataConverter, stateType, and raw instanceId
  • @JsonProperty("entityId") on getEntityInstanceId() (serializes via the new EntityInstanceId serializer)
  • @JsonProperty("state") on getState()

Fix (Samples): Changed CounterFunctions, BankAccountFunctions, and LifetimeFunctions to serialize via new JacksonDataConverter().serialize(entity) before passing to .body(), ensuring Jackson annotations are respected instead of relying on Gson.

Files changed: EntityMetadata.java, TypedEntityMetadata.java, CounterFunctions.java, BankAccountFunctions.java, LifetimeFunctions.java
Tests added: jacksonSerialization_* tests in TypedEntityMetadataTest.java

Testing

  • All existing unit tests pass (no breaking changes)
  • All entity integration tests pass against DTS emulator (11 tests)
  • Azure Functions TransferFunds sample verified end-to-end — entity locking now works correctly

Pull request checklist

  • My changes do not require documentation changes
    • Otherwise: Documentation issue linked to PR
  • My changes are added to the CHANGELOG.md
  • I have added all required tests (Unit tests, E2E tests)

Copilot AI review requested due to automatic review settings April 20, 2026 20:17
@bachuv bachuv requested a review from a team as a code owner April 20, 2026 20:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes several entity-related serialization/deserialization issues (especially in the Azure Functions trigger-binding code path) and adds Jackson JSON support for EntityInstanceId and entity metadata types to improve parity and usability across runtimes.

Changes:

  • Fix entity lock-grant handling in the Azure Functions path by special-casing AutoCloseable lock tasks in handleEventRaised.
  • Add Jackson serialization/deserialization support for EntityInstanceId, and add Jackson-friendly JSON surface for EntityMetadata / TypedEntityMetadata with new unit tests.
  • Update samples (standalone + Azure Functions) to use shared builder utilities / Jackson serialization for entity metadata, and update protobuf definitions used by the SDK.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
samples/src/main/java/io/durabletask/samples/TypedEntityProxySample.java Switch sample worker/client creation to centralized SampleUtils builders.
samples/src/main/java/io/durabletask/samples/SampleUtils.java New helper for creating client/worker builders based on env-driven connection string.
samples/src/main/java/io/durabletask/samples/LowLevelEntitySample.java Use SampleUtils builders for consistency across samples.
samples/src/main/java/io/durabletask/samples/EntityTimeoutSample.java Use SampleUtils builders for consistency across samples.
samples/src/main/java/io/durabletask/samples/EntityReentrantSample.java Use SampleUtils builders for consistency across samples.
samples/src/main/java/io/durabletask/samples/EntityQuerySample.java Use SampleUtils builders for consistency across samples.
samples/src/main/java/io/durabletask/samples/EntityCommunicationSample.java Use SampleUtils builders for consistency across samples.
samples/src/main/java/io/durabletask/samples/CounterEntitySample.java Use SampleUtils builders for consistency across samples.
samples/src/main/java/io/durabletask/samples/BankAccountSample.java Use SampleUtils builders and avoid swallowing framework control-flow exceptions.
samples/build.gradle Add Gradle run tasks for entity samples + set default ENDPOINT/TASKHUB environment.
samples-azure-functions/src/main/java/com/functions/entities/sensors.http Add HTTP request examples for the new sensor/aggregator Azure Functions sample.
samples-azure-functions/src/main/java/com/functions/entities/bankaccounts.http Add HTTP request examples for the bank account Azure Functions sample.
samples-azure-functions/src/main/java/com/functions/entities/SensorState.java New POJO state types used by sensor/aggregator entities.
samples-azure-functions/src/main/java/com/functions/entities/SensorFunctions.java New Azure Functions endpoints for sensor/aggregator entity communication scenario.
samples-azure-functions/src/main/java/com/functions/entities/SensorEntity.java New sensor entity implementation that forwards readings to an aggregator entity.
samples-azure-functions/src/main/java/com/functions/entities/LifetimeFunctions.java Serialize entity metadata via Jackson converter before returning in HTTP response.
samples-azure-functions/src/main/java/com/functions/entities/CounterFunctions.java Serialize entity metadata via Jackson converter before returning in HTTP response.
samples-azure-functions/src/main/java/com/functions/entities/BankAccountFunctions.java New Azure Functions sample demonstrating entity locking and metadata responses.
samples-azure-functions/src/main/java/com/functions/entities/BankAccountEntity.java New bank account entity implementation for the Azure Functions sample.
samples-azure-functions/src/main/java/com/functions/entities/AggregatorEntity.java New aggregator entity that computes averages and starts an alert orchestration.
internal/durabletask-protobuf/protos/orchestrator_service.proto Update proto contracts (tags, rewind action, deprecations, purge timeout).
internal/durabletask-protobuf/PROTO_SOURCE_COMMIT_HASH Update referenced upstream proto source commit hash.
client/src/test/java/com/microsoft/durabletask/TypedEntityMetadataTest.java Add Jackson serialization tests validating public vs internal fields.
client/src/test/java/com/microsoft/durabletask/TaskOrchestrationEntityEventTest.java Add regression test for lock grants arriving via EventRaised (Azure Functions path).
client/src/test/java/com/microsoft/durabletask/EntityInstanceIdTest.java Add Jackson round-trip tests for compact string EntityInstanceId JSON format.
client/src/main/java/com/microsoft/durabletask/TypedEntityMetadata.java Add Jackson annotations to expose state and hide stateType.
client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java Special-case lock grant handling for AutoCloseable tasks in handleEventRaised.
client/src/main/java/com/microsoft/durabletask/EntityMetadata.java Add Jackson annotations to hide internal fields and expose entityId.
client/src/main/java/com/microsoft/durabletask/EntityInstanceId.java Add Jackson serializer/deserializer for compact "@name@key" JSON representation.
client/build.gradle Bump client module version to 1.9.0.
azuremanaged/build.gradle Bump azuremanaged module version to 1.9.0.
azurefunctions/build.gradle Bump azurefunctions module version to 1.9.0.

Comment thread internal/durabletask-protobuf/protos/orchestrator_service.proto
Comment thread samples/build.gradle Outdated
Comment thread samples/src/main/java/io/durabletask/samples/SampleUtils.java Outdated
Comment thread samples-azure-functions/src/main/java/com/functions/entities/SensorState.java Outdated
sophiatev
sophiatev previously approved these changes Apr 22, 2026
@bachuv bachuv merged commit 84cdc37 into main Apr 22, 2026
8 checks passed
@bachuv bachuv deleted the vabachu/entity-lock-serialization branch April 22, 2026 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants