diff --git a/temporal-sdk/src/main/java/io/temporal/activity/ActivityOptions.java b/temporal-sdk/src/main/java/io/temporal/activity/ActivityOptions.java index 69032d1f7..f67a4beed 100644 --- a/temporal-sdk/src/main/java/io/temporal/activity/ActivityOptions.java +++ b/temporal-sdk/src/main/java/io/temporal/activity/ActivityOptions.java @@ -6,6 +6,7 @@ import io.temporal.common.context.ContextPropagator; import io.temporal.failure.CanceledFailure; import java.time.Duration; +import java.util.ArrayList; import java.util.List; /** Options used to configure how an activity is invoked. */ @@ -282,7 +283,9 @@ public Builder mergeActivityOptions(ActivityOptions override) { if (this.contextPropagators == null) { this.contextPropagators = override.contextPropagators; } else if (override.contextPropagators != null) { - this.contextPropagators.addAll(override.contextPropagators); + List merged = new ArrayList<>(this.contextPropagators); + merged.addAll(override.contextPropagators); + this.contextPropagators = merged; } if (override.versioningIntent != VersioningIntent.VERSIONING_INTENT_UNSPECIFIED) { this.versioningIntent = override.versioningIntent; diff --git a/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsTest.java b/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsTest.java index 26f8cc910..3fdaf9e9a 100644 --- a/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsTest.java +++ b/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsTest.java @@ -2,13 +2,17 @@ import static org.junit.Assert.*; +import io.temporal.api.common.v1.Payload; import io.temporal.common.MethodRetry; import io.temporal.common.RetryOptions; +import io.temporal.common.context.ContextPropagator; import io.temporal.testing.TestActivityEnvironment; import io.temporal.workflow.shared.TestActivities.TestActivity; import io.temporal.workflow.shared.TestActivities.TestActivityImpl; import java.lang.reflect.Method; import java.time.Duration; +import java.util.Collections; +import java.util.List; import java.util.Map; import org.junit.*; import org.junit.rules.Timeout; @@ -62,6 +66,87 @@ public void testActivityOptionsMerge() { Assert.assertEquals(methodOps1, merged); } + @Test + public void testActivityOptionsMergeWithImmutableContextPropagators() { + // Create simple test context propagators + ContextPropagator propagator1 = + new ContextPropagator() { + @Override + public String getName() { + return "propagator1"; + } + + @Override + public Map serializeContext(Object context) { + return Collections.emptyMap(); + } + + @Override + public Object deserializeContext(Map context) { + return null; + } + + @Override + public Object getCurrentContext() { + return null; + } + + @Override + public void setCurrentContext(Object context) {} + }; + + ContextPropagator propagator2 = + new ContextPropagator() { + @Override + public String getName() { + return "propagator2"; + } + + @Override + public Map serializeContext(Object context) { + return Collections.emptyMap(); + } + + @Override + public Object deserializeContext(Map context) { + return null; + } + + @Override + public Object getCurrentContext() { + return null; + } + + @Override + public void setCurrentContext(Object context) {} + }; + + // Create options with immutable singleton lists + // This tests the fix for https://github.com/temporalio/sdk-java/issues/2482 + ActivityOptions options1 = + ActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofSeconds(1)) + .setContextPropagators(Collections.singletonList(propagator1)) + .build(); + + ActivityOptions options2 = + ActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofSeconds(2)) + .setContextPropagators(Collections.singletonList(propagator2)) + .build(); + + // Merging should not throw UnsupportedOperationException + ActivityOptions merged = + ActivityOptions.newBuilder(options1).mergeActivityOptions(options2).build(); + + // Verify both context propagators are present in the merged result + List mergedPropagators = merged.getContextPropagators(); + assertNotNull(mergedPropagators); + assertEquals(2, mergedPropagators.size()); + assertEquals("propagator1", mergedPropagators.get(0).getName()); + assertEquals("propagator2", mergedPropagators.get(1).getName()); + } + @Test public void testActivityOptionsDefaultInstance() { testEnv.registerActivitiesImplementations(new TestActivityImpl());