From 2e24a37ef7fe2a6223418950b1b18c787c51d18b Mon Sep 17 00:00:00 2001 From: Nitish Agarwal <1592163+nitishagar@users.noreply.github.com> Date: Fri, 26 Dec 2025 13:26:02 +0530 Subject: [PATCH] Fix UnsupportedOperationException when merging ActivityOptions with immutable context propagators The mergeActivityOptions method was calling addAll() directly on the existing contextPropagators list, which fails when the list is immutable (e.g., created with List.of() or Collections.emptyList()). This fix creates a new ArrayList that combines both lists instead of modifying the existing one. Fixes #2482 --- .../io/temporal/activity/ActivityOptions.java | 5 +++- .../activity/ActivityOptionsTest.java | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) 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..3a96c685f 100644 --- a/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsTest.java +++ b/temporal-sdk/src/test/java/io/temporal/activity/ActivityOptionsTest.java @@ -9,6 +9,7 @@ import io.temporal.workflow.shared.TestActivities.TestActivityImpl; import java.lang.reflect.Method; import java.time.Duration; +import java.util.Collections; import java.util.Map; import org.junit.*; import org.junit.rules.Timeout; @@ -62,6 +63,31 @@ public void testActivityOptionsMerge() { Assert.assertEquals(methodOps1, merged); } + @Test + public void testActivityOptionsMergeWithImmutableContextPropagators() { + // Create options with immutable lists using Collections.emptyList() + // This tests the fix for https://github.com/temporalio/sdk-java/issues/2482 + ActivityOptions options1 = + ActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofSeconds(1)) + .setContextPropagators(Collections.emptyList()) + .build(); + + ActivityOptions options2 = + ActivityOptions.newBuilder() + .setStartToCloseTimeout(Duration.ofSeconds(2)) + .setContextPropagators(Collections.emptyList()) + .build(); + + // This should NOT throw UnsupportedOperationException + ActivityOptions merged = + ActivityOptions.newBuilder(options1).mergeActivityOptions(options2).build(); + + assertNotNull(merged); + assertNotNull(merged.getContextPropagators()); + assertEquals(Duration.ofSeconds(2), merged.getStartToCloseTimeout()); + } + @Test public void testActivityOptionsDefaultInstance() { testEnv.registerActivitiesImplementations(new TestActivityImpl());