Repository navigation
Description
Expected Behavior
DurableContext.map(name, items, Class<O>, fn, config) returns a MapResult<O> whose items are instances of O. This holds on the first execution and on replay.
Actual Behavior
When O is a POJO or record, each item in the returned MapResult is a java.util.LinkedHashMap. Reading an item as O throws ClassCastException.
This happens on the first execution, not only on replay. On completion, MapOperation replaces its result with a serialize-then-deserialize copy, so the first execution returns the same value that replay returns.
Steps to Reproduce
public record Payload(String value) {}
@Test
void itemIsDeclaredType() {
var runner = LocalDurableTestRunner.create(String.class, (input, context) -> {
var result = context.map(
"m",
List.of("a", "b"),
Payload.class,
(item, index, ctx) -> new Payload(item),
MapConfig.builder().build());
Object first = result.getResult(0);
return first.getClass().getName();
});
var result = runner.runUntilComplete("x");
assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus());
assertEquals(Payload.class.getName(), result.getResult(String.class));
}The test fails:
expected: <...$Payload> but was: <java.util.LinkedHashMap>
SDK Version
main at 3.0.1-SNAPSHOT. Also reported on 2.x. On 1.2.1 the first execution returns Payload, and replay of a completed map returns LinkedHashMap.
Java Version
17
Is this a regression?
Yes
Last Working Version
None fully. 1.2.1 is correct on the first execution and wrong on replay.
Additional Context
The cause is in MapOperation. The constructor passes new TypeToken<>() {} to its superclass as the result type (sdk/src/main/java/software/amazon/lambda/durable/operation/MapOperation.java, line 83).
- That anonymous class is created inside the generic class
MapOperation<I, O>. - So the captured type is
MapResult<O>, withOstill a type variable. - So Jackson has no item type and deserializes each item as a
LinkedHashMap. - The constructor receives the caller's
TypeToken<O>asitemResultType. It stores it but does not use it to build the result type.
The existing MapIntegrationTest cases use String and Integer items. Jackson deserializes those correctly without type information, so the tests do not catch the bug.
The JavaScript SDK also returns the serdes round-trip of the batch result on both paths. Its items are plain JSON objects, which already match the declared TypeScript type. So keeping the round-trip and fixing the item type keeps Java consistent with JavaScript.
Suggested fix: build a parameterized MapResult<O> type from itemResultType, for example by letting TypeToken be created from a java.lang.reflect.Type. Add integration tests with a POJO item type for the first execution and for replay.
Activity