← Files TemporalARCHIVED FILE
skills/temporal-developer/references/java/data-handling.md
8.8 KB · Oct 2, 2026 · 00:08 UTC
# Java SDK Data Handling
## Overview
The Java SDK uses data converters to serialize/deserialize workflow inputs, outputs, and activity parameters. The `DataConverter` interface controls how values are converted to and from Temporal `Payload` protobufs.
## Default Data Converter
`DefaultDataConverter` applies converters in order, using the first that accepts the value:
1. `NullPayloadConverter` — `null` values
2. `ByteArrayPayloadConverter` — `byte[]` as raw binary
3. `ProtobufJsonPayloadConverter` — Protobuf `Message` instances as JSON
4. `ProtobufPayloadConverter` — Protobuf `Message` instances as binary
5. `JacksonJsonPayloadConverter` — Everything else via Jackson `ObjectMapper`
## Jackson Integration
Use `JacksonJsonPayloadConverter` with a custom `ObjectMapper` for advanced serialization (e.g., Java 8 time module, custom serializers):
```java
ObjectMapper mapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
DefaultDataConverter converter = DefaultDataConverter.newDefaultInstance()
.withPayloadConverterOverrides(
new JacksonJsonPayloadConverter(mapper)
);
WorkflowServiceStubs service = WorkflowServiceStubs.newLocalServiceStubs();
WorkflowClient client = WorkflowClient.newInstance(
service,
WorkflowClientOptions.newBuilder()
.setDataConverter(converter)
.build()
);
```
## Custom Data Converter
Implement `PayloadConverter` for custom serialization:
```java
public class MyCustomPayloadConverter implements PayloadConverter {
@Override
public String getEncodingType() {
return "json/my-custom";
}
@Override
public Optional<Payload> toData(Object value) throws DataConverterException {
// Return Optional.empty() if this converter doesn't handle the type
if (!(value instanceof MyCustomType)) {
return Optional.empty();
}
// Serialize to Payload
byte[] data = serialize(value);
return Optional.of(
Payload.newBuilder()
.putMetadata("encoding", ByteString.copyFromUtf8(getEncodingType()))
.setData(ByteString.copyFrom(data))
.build()
);
}
@Override
public <T> T fromData(Payload content, Class<T> valueClass, Type valueType)
throws DataConverterException {
// Deserialize from Payload
return deserialize(content.getData().toByteArray(), valueClass);
}
}
```
Override specific converters in the default chain:
```java
DefaultDataConverter converter = DefaultDataConverter.newDefaultInstance()
.withPayloadConverterOverrides(new MyCustomPayloadConverter());
```
## Composition of Payload Converters
`DefaultDataConverter` holds a list of `PayloadConverter` instances tried in order. The first converter whose `toData()` returns a non-empty `Optional` wins. When using `withPayloadConverterOverrides()`, converters with matching encoding types replace existing ones.
```java
DefaultDataConverter converter = DefaultDataConverter.newDefaultInstance()
.withPayloadConverterOverrides(
new MyCustomPayloadConverter(), // encoding: "json/my-custom"
new JacksonJsonPayloadConverter(mapper) // replaces default Jackson converter
);
```
## Protobuf Support
Protobuf messages are handled by `ProtobufJsonPayloadConverter` (enabled by default). It serializes `com.google.protobuf.Message` instances as JSON for human readability in the Temporal UI.
```java
// Protobuf messages work out of the box as workflow/activity params
@WorkflowInterface
public interface MyWorkflow {
@WorkflowMethod
MyProtoResult run(MyProtoInput input);
}
```
For binary protobuf encoding instead of JSON, use `ProtobufPayloadConverter`:
```java
DefaultDataConverter converter = DefaultDataConverter.newDefaultInstance()
.withPayloadConverterOverrides(new ProtobufPayloadConverter());
```
## Payload Encryption
Use `PayloadCodec` with `CodecDataConverter` to encrypt/compress payloads:
```java
public class EncryptionCodec implements PayloadCodec {
private final SecretKey key;
public EncryptionCodec(SecretKey key) {
this.key = key;
}
@Override
public List<Payload> encode(List<Payload> payloads) {
return payloads.stream().map(payload -> {
// Encrypt payload.toByteArray() using your chosen algorithm (e.g., AES/GCM)
byte[] encrypted = encryptBytes(payload.toByteArray(), key);
return Payload.newBuilder()
.putMetadata("encoding", ByteString.copyFromUtf8("binary/encrypted"))
.setData(ByteString.copyFrom(encrypted))
.build();
}).collect(Collectors.toList());
}
@Override
public List<Payload> decode(List<Payload> payloads) {
return payloads.stream().map(payload -> {
String encoding = payload.getMetadataOrDefault(
"encoding", ByteString.EMPTY).toStringUtf8();
if (!"binary/encrypted".equals(encoding)) return payload;
// Decrypt and reconstruct the original Payload
byte[] decrypted = decryptBytes(payload.getData().toByteArray(), key);
return Payload.parseFrom(decrypted);
}).collect(Collectors.toList());
}
}
```
Apply the codec to the client:
```java
CodecDataConverter codecDataConverter = new CodecDataConverter(
DefaultDataConverter.newDefaultInstance(),
Collections.singletonList(new EncryptionCodec(secretKey))
);
WorkflowClient client = WorkflowClient.newInstance(
service,
WorkflowClientOptions.newBuilder()
.setDataConverter(codecDataConverter)
.build()
);
```
## Search Attributes
Custom searchable fields for workflow visibility.
```java
import io.temporal.common.SearchAttributeKey;
import io.temporal.common.SearchAttributes;
// Define typed search attribute keys
static final SearchAttributeKey<String> ORDER_ID =
SearchAttributeKey.forKeyword("OrderId");
static final SearchAttributeKey<String> ORDER_STATUS =
SearchAttributeKey.forKeyword("OrderStatus");
static final SearchAttributeKey<Double> ORDER_TOTAL =
SearchAttributeKey.forDouble("OrderTotal");
static final SearchAttributeKey<OffsetDateTime> CREATED_AT =
SearchAttributeKey.forOffsetDateTime("CreatedAt");
// Set at workflow start
WorkflowOptions options = WorkflowOptions.newBuilder()
.setWorkflowId("order-" + orderId)
.setTaskQueue("orders")
.setTypedSearchAttributes(
SearchAttributes.newBuilder()
.set(ORDER_ID, orderId)
.set(ORDER_STATUS, "pending")
.set(ORDER_TOTAL, 99.99)
.set(CREATED_AT, OffsetDateTime.now())
.build()
)
.build();
```
Upsert during workflow execution:
```java
@WorkflowInterface
public interface OrderWorkflow {
@WorkflowMethod
String run(Order order);
}
public class OrderWorkflowImpl implements OrderWorkflow {
static final SearchAttributeKey<String> ORDER_STATUS =
SearchAttributeKey.forKeyword("OrderStatus");
@Override
public String run(Order order) {
// ... process order ...
Workflow.upsertTypedSearchAttributes(
ORDER_STATUS.valueSet("completed")
);
return "done";
}
}
```
### Querying Workflows by Search Attributes
```java
ListWorkflowExecutionsRequest request = ListWorkflowExecutionsRequest.newBuilder()
.setNamespace("default")
.setQuery("OrderStatus = 'processing' OR OrderStatus = 'pending'")
.build();
```
## Workflow Memo
Store arbitrary metadata with workflows (not searchable).
```java
// Set memo at workflow start
WorkflowOptions options = WorkflowOptions.newBuilder()
.setWorkflowId("order-" + orderId)
.setTaskQueue("orders")
.setMemo(Map.of(
"customer_name", order.getCustomerName(),
"notes", "Priority customer"
))
.build();
```
```java
// Read memo from workflow
@Override
public String run(Order order) {
String notes = Workflow.getMemo("notes", String.class);
// ...
}
```
## Deterministic APIs for Values
Use these APIs within workflows for deterministic values:
```java
@Override
public String run() {
// Deterministic UUID (same on replay)
String uniqueId = Workflow.randomUUID().toString();
// Deterministic random (same on replay)
Random rng = Workflow.newRandom();
int value = rng.nextInt(100);
// Deterministic current time (same on replay)
long now = Workflow.currentTimeMillis();
return uniqueId;
}
```
## Best Practices
1. Use Jackson `ObjectMapper` customization for complex serialization needs
2. Keep payloads small — see `references/core/gotchas.md` for limits
3. Encrypt sensitive data with `PayloadCodec` and `CodecDataConverter`
4. Use POJOs or Protobuf messages for workflow/activity parameters
5. Use `Workflow.randomUUID()`, `Workflow.newRandom()`, and `Workflow.currentTimeMillis()` for deterministic values
SHA-256: fd1254c4d955215536203b231c13c0e53e48f4238e45d550282ae26d571cc0e4