Skip to content

Commit 8d76f4e

Browse files
Add patch 0005: Wire protobuf support
WirePayloadConverter for binary encoding of Wire Message types. WireJsonAdapterFactory registered on MoshiJsonPayloadConverter. Wire version added to overlay/gradle.properties for Renovate. apply-patches.sh now excludes overlay files from git tracking in build/.
1 parent d8c5cb1 commit 8d76f4e

7 files changed

Lines changed: 324 additions & 5 deletions

overlay/gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
moshiVersion=1.15.2
2+
wireVersion=6.4.0

patches/0001-Replace-Jackson-with-Moshi.patch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
From 4b9650f8e62987e1bbd096b5eb6852c7cf9b712b Mon Sep 17 00:00:00 2001
1+
From bc22c6b45ed37d711c7709a08adde58e5eeaa79a Mon Sep 17 00:00:00 2001
22
From: Marius Volkhart <marius.volkhart@pkware.com>
33
Date: Thu, 21 May 2026 14:36:55 -0400
44
Subject: [PATCH] Replace Jackson with Moshi

patches/0002-Remove-GSON-dependency.patch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
From 5f6f91a3eddf940d2bc5bbc78600f8153a673bff Mon Sep 17 00:00:00 2001
1+
From bd4185b0b16c6d261e5c9644b549df73b004c7e9 Mon Sep 17 00:00:00 2001
22
From: Marius Volkhart <marius.volkhart@pkware.com>
33
Date: Thu, 21 May 2026 14:41:44 -0400
44
Subject: [PATCH] Remove GSON dependency

patches/0003-Replace-grpc-netty-shaded-with-grpc-okhttp.patch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
From dc7292b1cf604d39b2e1e0f6129b02ace3553443 Mon Sep 17 00:00:00 2001
1+
From 272c60a9e7cc1b2b0a65587b6254e8f183b9709d Mon Sep 17 00:00:00 2001
22
From: Marius Volkhart <marius.volkhart@pkware.com>
33
Date: Thu, 21 May 2026 14:53:32 -0400
44
Subject: [PATCH] Replace grpc-netty-shaded with grpc-okhttp

patches/0004-Change-groupId-to-com.pkware.temporal.patch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
From ede8dcc4a58c9d21db70190018a854a9e52fdb93 Mon Sep 17 00:00:00 2001
1+
From 402ce4c914add0e25438c58aaaa92033626cab16 Mon Sep 17 00:00:00 2001
22
From: Marius Volkhart <marius.volkhart@pkware.com>
33
Date: Thu, 21 May 2026 14:53:54 -0400
44
Subject: [PATCH] Change groupId to com.pkware.temporal
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
From dc084a358eeaebbec93f529381851c5f7d48a4b1 Mon Sep 17 00:00:00 2001
2+
From: Marius Volkhart <marius.volkhart@pkware.com>
3+
Date: Thu, 28 May 2026 11:52:21 -0400
4+
Subject: [PATCH] Add Wire protobuf support
5+
6+
WirePayloadConverter handles com.squareup.wire.Message types using
7+
binary encoding (protobuf/wire). Added to STANDARD_PAYLOAD_CONVERTERS
8+
between ProtobufPayloadConverter and MoshiJsonPayloadConverter.
9+
10+
Wire's WireJsonAdapterFactory registered on MoshiJsonPayloadConverter
11+
so Wire types also work through JSON path.
12+
13+
Adds wire-runtime and wire-moshi-adapter as api dependencies.
14+
Wire Gradle plugin compiles test .proto for WirePayloadConverterTest.
15+
---
16+
build.gradle | 6 ++
17+
temporal-sdk/build.gradle | 15 +++
18+
.../converter/DefaultDataConverter.java | 1 +
19+
.../converter/MoshiJsonPayloadConverter.java | 3 +
20+
.../converter/WirePayloadConverter.java | 61 +++++++++++
21+
.../converter/WirePayloadConverterTest.java | 100 ++++++++++++++++++
22+
temporal-sdk/src/test/proto/test_wire.proto | 15 +++
23+
7 files changed, 201 insertions(+)
24+
create mode 100644 temporal-sdk/src/main/java/io/temporal/common/converter/WirePayloadConverter.java
25+
create mode 100644 temporal-sdk/src/test/java/io/temporal/common/converter/WirePayloadConverterTest.java
26+
create mode 100644 temporal-sdk/src/test/proto/test_wire.proto
27+
28+
diff --git a/build.gradle b/build.gradle
29+
index 6dcbcc7..a304c21 100644
30+
--- a/build.gradle
31+
+++ b/build.gradle
32+
@@ -1,3 +1,9 @@
33+
+buildscript {
34+
+ dependencies {
35+
+ classpath "com.squareup.wire:wire-gradle-plugin:$wireVersion"
36+
+ }
37+
+}
38+
+
39+
plugins {
40+
id 'net.ltgt.errorprone' version '4.1.0' apply false
41+
id 'io.github.gradle-nexus.publish-plugin' version '1.3.0'
42+
diff --git a/temporal-sdk/build.gradle b/temporal-sdk/build.gradle
43+
index ea02cdf..fc9da30 100644
44+
--- a/temporal-sdk/build.gradle
45+
+++ b/temporal-sdk/build.gradle
46+
@@ -1,5 +1,18 @@
47+
+apply plugin: 'com.squareup.wire'
48+
+
49+
description = '''Temporal Workflow Java SDK'''
50+
51+
+wire {
52+
+ sourcePath {
53+
+ srcDir 'src/test/proto'
54+
+ }
55+
+ java {
56+
+ out = "${buildDir}/generated/source/wire"
57+
+ }
58+
+}
59+
+
60+
+sourceSets.test.java.srcDir("${buildDir}/generated/source/wire")
61+
+
62+
dependencies {
63+
api(platform("io.grpc:grpc-bom:$grpcVersion"))
64+
api(platform("io.micrometer:micrometer-bom:$micrometerVersion"))
65+
@@ -10,6 +23,8 @@ dependencies {
66+
67+
implementation "com.google.guava:guava:$guavaVersion"
68+
api "com.squareup.moshi:moshi:$moshiVersion"
69+
+ api "com.squareup.wire:wire-runtime:$wireVersion"
70+
+ api "com.squareup.wire:wire-moshi-adapter:$wireVersion"
71+
72+
// compileOnly and testImplementation because this dependency is needed only to work with json format of history
73+
// which shouldn't be needed for any production usage of temporal-sdk.
74+
diff --git a/temporal-sdk/src/main/java/io/temporal/common/converter/DefaultDataConverter.java b/temporal-sdk/src/main/java/io/temporal/common/converter/DefaultDataConverter.java
75+
index d86e948..b8e0b11 100644
76+
--- a/temporal-sdk/src/main/java/io/temporal/common/converter/DefaultDataConverter.java
77+
+++ b/temporal-sdk/src/main/java/io/temporal/common/converter/DefaultDataConverter.java
78+
@@ -18,6 +18,7 @@ public class DefaultDataConverter extends PayloadAndFailureDataConverter {
79+
new ByteArrayPayloadConverter(),
80+
new ProtobufJsonPayloadConverter(),
81+
new ProtobufPayloadConverter(),
82+
+ new WirePayloadConverter(),
83+
new MoshiJsonPayloadConverter()
84+
};
85+
86+
diff --git a/temporal-sdk/src/main/java/io/temporal/common/converter/MoshiJsonPayloadConverter.java b/temporal-sdk/src/main/java/io/temporal/common/converter/MoshiJsonPayloadConverter.java
87+
index f81922d..019e163 100644
88+
--- a/temporal-sdk/src/main/java/io/temporal/common/converter/MoshiJsonPayloadConverter.java
89+
+++ b/temporal-sdk/src/main/java/io/temporal/common/converter/MoshiJsonPayloadConverter.java
90+
@@ -3,6 +3,7 @@ package io.temporal.common.converter;
91+
import com.google.protobuf.ByteString;
92+
import com.squareup.moshi.JsonAdapter;
93+
import com.squareup.moshi.Moshi;
94+
+import com.squareup.wire.WireJsonAdapterFactory;
95+
import io.temporal.api.common.v1.Payload;
96+
import java.io.IOException;
97+
import java.lang.reflect.Type;
98+
@@ -29,6 +30,7 @@ public class MoshiJsonPayloadConverter implements PayloadConverter {
99+
this.moshi =
100+
userMoshi
101+
.newBuilder()
102+
+ .add(new WireJsonAdapterFactory())
103+
.add(new DurationMillisAdapter())
104+
.add(new OperationTokenTypeAdapter())
105+
.build();
106+
@@ -41,6 +43,7 @@ public class MoshiJsonPayloadConverter implements PayloadConverter {
107+
*/
108+
public static Moshi newDefaultMoshi() {
109+
return new Moshi.Builder()
110+
+ .add(new WireJsonAdapterFactory())
111+
.add(new DurationMillisAdapter())
112+
.add(new OperationTokenTypeAdapter())
113+
.build();
114+
diff --git a/temporal-sdk/src/main/java/io/temporal/common/converter/WirePayloadConverter.java b/temporal-sdk/src/main/java/io/temporal/common/converter/WirePayloadConverter.java
115+
new file mode 100644
116+
index 0000000..82c8a24
117+
--- /dev/null
118+
+++ b/temporal-sdk/src/main/java/io/temporal/common/converter/WirePayloadConverter.java
119+
@@ -0,0 +1,61 @@
120+
+package io.temporal.common.converter;
121+
+
122+
+import com.google.protobuf.ByteString;
123+
+import com.squareup.wire.Message;
124+
+import com.squareup.wire.ProtoAdapter;
125+
+import io.temporal.api.common.v1.Payload;
126+
+import java.io.IOException;
127+
+import java.lang.reflect.Type;
128+
+import java.nio.charset.StandardCharsets;
129+
+import java.util.Optional;
130+
+
131+
+/**
132+
+ * {@link PayloadConverter} for Wire-generated protobuf types.
133+
+ *
134+
+ * <p>Uses Wire's native binary encoding via each message's companion {@link ProtoAdapter}. Non-Wire
135+
+ * types are skipped (returns {@link Optional#empty()}) so the converter chain falls through to the
136+
+ * next converter.
137+
+ */
138+
+public class WirePayloadConverter implements PayloadConverter {
139+
+ private static final String ENCODING_TYPE = "protobuf/wire";
140+
+ private static final ByteString ENCODING_METADATA =
141+
+ ByteString.copyFrom(ENCODING_TYPE, StandardCharsets.UTF_8);
142+
+
143+
+ @Override
144+
+ public String getEncodingType() {
145+
+ return ENCODING_TYPE;
146+
+ }
147+
+
148+
+ @Override
149+
+ @SuppressWarnings({"unchecked", "rawtypes"})
150+
+ public Optional<Payload> toData(Object value) throws DataConverterException {
151+
+ if (value == null || !(value instanceof Message)) return Optional.empty();
152+
+ try {
153+
+ ProtoAdapter adapter = (ProtoAdapter) value.getClass().getField("ADAPTER").get(null);
154+
+ byte[] bytes = adapter.encode(value);
155+
+ return Optional.of(
156+
+ Payload.newBuilder()
157+
+ .putMetadata(EncodingKeys.METADATA_ENCODING_KEY, ENCODING_METADATA)
158+
+ .setData(ByteString.copyFrom(bytes))
159+
+ .build());
160+
+ } catch (NoSuchFieldException | IllegalAccessException e) {
161+
+ throw new DataConverterException(
162+
+ "Class " + value.getClass().getName() + " is not a Wire-generated message", e);
163+
+ }
164+
+ }
165+
+
166+
+ @Override
167+
+ @SuppressWarnings("unchecked")
168+
+ public <T> T fromData(Payload content, Class<T> valueClass, Type valueType)
169+
+ throws DataConverterException {
170+
+ try {
171+
+ ProtoAdapter<T> adapter = (ProtoAdapter<T>) valueClass.getField("ADAPTER").get(null);
172+
+ return adapter.decode(content.getData().toByteArray());
173+
+ } catch (IOException e) {
174+
+ throw new DataConverterException(e);
175+
+ } catch (NoSuchFieldException | IllegalAccessException e) {
176+
+ throw new DataConverterException(
177+
+ "Class " + valueClass.getName() + " is not a Wire-generated message", e);
178+
+ }
179+
+ }
180+
+}
181+
diff --git a/temporal-sdk/src/test/java/io/temporal/common/converter/WirePayloadConverterTest.java b/temporal-sdk/src/test/java/io/temporal/common/converter/WirePayloadConverterTest.java
182+
new file mode 100644
183+
index 0000000..3fe57cb
184+
--- /dev/null
185+
+++ b/temporal-sdk/src/test/java/io/temporal/common/converter/WirePayloadConverterTest.java
186+
@@ -0,0 +1,100 @@
187+
+package io.temporal.common.converter;
188+
+
189+
+import static org.junit.Assert.*;
190+
+
191+
+import com.google.protobuf.ByteString;
192+
+import io.temporal.api.common.v1.Payload;
193+
+import io.temporal.common.converter.wiretest.NestedMessage;
194+
+import io.temporal.common.converter.wiretest.SimpleMessage;
195+
+import java.nio.charset.StandardCharsets;
196+
+import java.util.Arrays;
197+
+import java.util.Optional;
198+
+import org.junit.Test;
199+
+
200+
+public class WirePayloadConverterTest {
201+
+
202+
+ private final WirePayloadConverter converter = new WirePayloadConverter();
203+
+
204+
+ @Test
205+
+ public void encodingTypeIsProtobufWire() {
206+
+ assertEquals("protobuf/wire", converter.getEncodingType());
207+
+ }
208+
+
209+
+ @Test
210+
+ public void nullReturnsEmpty() {
211+
+ assertFalse(converter.toData(null).isPresent());
212+
+ }
213+
+
214+
+ @Test
215+
+ public void nonWireTypeReturnsEmpty() {
216+
+ assertFalse(converter.toData("just a string").isPresent());
217+
+ }
218+
+
219+
+ @Test
220+
+ public void serializesWireMessage() {
221+
+ SimpleMessage msg = new SimpleMessage.Builder().name("Alice").value(30).build();
222+
+
223+
+ Optional<Payload> result = converter.toData(msg);
224+
+
225+
+ assertTrue(result.isPresent());
226+
+ String encoding =
227+
+ result
228+
+ .get()
229+
+ .getMetadataMap()
230+
+ .get(EncodingKeys.METADATA_ENCODING_KEY)
231+
+ .toString(StandardCharsets.UTF_8);
232+
+ assertEquals("protobuf/wire", encoding);
233+
+ assertFalse(result.get().getData().isEmpty());
234+
+ }
235+
+
236+
+ @Test
237+
+ public void roundTripsSimpleMessage() {
238+
+ SimpleMessage original = new SimpleMessage.Builder().name("Bob").value(42).build();
239+
+
240+
+ Payload payload = converter.toData(original).get();
241+
+ SimpleMessage restored = converter.fromData(payload, SimpleMessage.class, SimpleMessage.class);
242+
+
243+
+ assertEquals(original, restored);
244+
+ }
245+
+
246+
+ @Test
247+
+ public void roundTripsNestedMessage() {
248+
+ NestedMessage original =
249+
+ new NestedMessage.Builder()
250+
+ .id("test-123")
251+
+ .items(
252+
+ Arrays.asList(
253+
+ new SimpleMessage.Builder().name("a").value(1).build(),
254+
+ new SimpleMessage.Builder().name("b").value(2).build()))
255+
+ .build();
256+
+
257+
+ Payload payload = converter.toData(original).get();
258+
+ NestedMessage restored = converter.fromData(payload, NestedMessage.class, NestedMessage.class);
259+
+
260+
+ assertEquals(original, restored);
261+
+ }
262+
+
263+
+ @Test
264+
+ public void decodesEmptyPayloadAsIdentityMessage() {
265+
+ Payload payload = wirePayload(new byte[0]);
266+
+
267+
+ SimpleMessage result = converter.fromData(payload, SimpleMessage.class, SimpleMessage.class);
268+
+
269+
+ assertEquals(new SimpleMessage.Builder().build(), result);
270+
+ }
271+
+
272+
+ @Test(expected = DataConverterException.class)
273+
+ public void corruptedBinaryThrows() {
274+
+ Payload payload = wirePayload(new byte[] {(byte) 0xFF, (byte) 0xFE, 0x01, 0x02, 0x03});
275+
+ converter.fromData(payload, SimpleMessage.class, SimpleMessage.class);
276+
+ }
277+
+
278+
+ private static Payload wirePayload(byte[] bytes) {
279+
+ return Payload.newBuilder()
280+
+ .putMetadata(
281+
+ EncodingKeys.METADATA_ENCODING_KEY,
282+
+ ByteString.copyFrom("protobuf/wire", StandardCharsets.UTF_8))
283+
+ .setData(ByteString.copyFrom(bytes))
284+
+ .build();
285+
+ }
286+
+}
287+
diff --git a/temporal-sdk/src/test/proto/test_wire.proto b/temporal-sdk/src/test/proto/test_wire.proto
288+
new file mode 100644
289+
index 0000000..d08747e
290+
--- /dev/null
291+
+++ b/temporal-sdk/src/test/proto/test_wire.proto
292+
@@ -0,0 +1,15 @@
293+
+syntax = "proto3";
294+
+
295+
+package io.temporal.common.converter.wiretest;
296+
+
297+
+option java_package = "io.temporal.common.converter.wiretest";
298+
+
299+
+message SimpleMessage {
300+
+ string name = 1;
301+
+ int32 value = 2;
302+
+}
303+
+
304+
+message NestedMessage {
305+
+ string id = 1;
306+
+ repeated SimpleMessage items = 2;
307+
+}
308+
--
309+
2.50.1 (Apple Git-155)
310+

scripts/apply-patches.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,18 @@ else
6464
echo "==> No patches to apply."
6565
fi
6666

67-
# Copy overlay files
67+
# Copy overlay files (not tracked by git — these are build-time config, not patches)
6868
if [ -d "$OVERLAY_DIR" ] && [ "$(ls -A "$OVERLAY_DIR")" ]; then
6969
echo "==> Copying overlay files..."
7070
cp -R "$OVERLAY_DIR"/* "$BUILD_DIR"/
71+
72+
# Prevent overlay files from being accidentally committed as patches
73+
cd "$BUILD_DIR"
74+
for f in $(cd "$OVERLAY_DIR" && find . -type f | sed 's|^\./||'); do
75+
if ! git check-ignore -q "$f" 2>/dev/null; then
76+
echo "$f" >> .git/info/exclude
77+
fi
78+
done
7179
fi
7280

7381
echo ""

0 commit comments

Comments
 (0)