SDK Type Adapters
Type Adapters allow the LittleHorse SDK to convert between your types and LittleHorse's native typing system.
✅ With Type Adapters, you can use any Java class directly in @LHTaskMethod signatures and @LHStructDef fields. You define how to convert that class to and from a supported LittleHorse variable type, and the SDK handles the rest. This leads to cleaner code and better type safety across your workflows.
❌ Before Type Adapters, task methods and workflow variables were limited to types the SDK natively understood—String, Long, Boolean, byte[], etc... If the SDK didn't recognize a type, it would fallback to JSON serialization/deserialization, which has unexpected results with some types.
We are currently rolling out the Type Adapter feature to all SDKs. The Java SDK has full support for Type Adapters, while other language SDKs will gain support in the coming months.
When to Use Type Adapters
Use Type Adapters when you want to work with types that don't have a direct mapping to a LittleHorse's typing system. Common examples include:
- Value objects:
UUID,Email,Money, or other domain-specific types. - Date/time types:
ZonedDateTime,Period,Duration, etc... - Third-party library types: Any class from an external library that you want to pass through LittleHorse workflows.
- Custom enums: Enumerations that map naturally to a string or integer representation.
Without an adapter, using a UUID as a task method parameter would require manually converting it to and from String in your task code. With an adapter registered, the SDK handles this conversion automatically wherever the type is encountered—in task definitions, task execution, workflow variables, and @LHStructDef fields.
Type Adapter Interfaces
Each LittleHorse VariableType has a corresponding adapter interface. The table below lists all available adapter types:
| Adapter Interface | LH Variable Type | Conversion Methods |
|---|---|---|
LHStringAdapter<T> | STR | toString(T) / fromString(String) |
LHLongAdapter<T> | INT | toLong(T) / fromLong(Long) |
LHIntegerAdapter<T> | INT | toInteger(T) / fromInteger(Integer) |
LHDoubleAdapter<T> | DOUBLE | toDouble(T) / fromDouble(Double) |
LHBooleanAdapter<T> | BOOL | toBoolean(T) / fromBoolean(Boolean) |
LHBytesAdapter<T> | BYTES | toBytes(T) / fromBytes(byte[]) |
LHTimestampAdapter<T> | TIMESTAMP | toTimestamp(T) / fromTimestamp(Timestamp) |
LHJsonObjAdapter<T> | JSON_OBJ | toJsonObj(T) / fromJsonObj(Object) |
LHJsonArrAdapter<T> | JSON_ARR | toJsonArr(T) / fromJsonArr(List) |
LHWfRunIdAdapter<T> | WF_RUN_ID | toWfRunId(T) / fromWfRunId(WfRunId) |
Choose the adapter interface that matches the LittleHorse variable type you want your Java class to map to. For example, if your custom type should be stored as a STR in LittleHorse, implement LHStringAdapter<T>.
Creating a Custom Type Adapter
This section walks through creating and registering a Type Adapter for java.util.UUID, which maps to LittleHorse's STR type.
Step 1: Choose the Adapter Interface
Since a UUID is naturally represented as a string, we use LHStringAdapter<UUID>:
import io.littlehorse.sdk.worker.adapter.LHStringAdapter;
import java.util.UUID;
public class UUIDAdapter implements LHStringAdapter<UUID> {
@Override
public String toString(UUID src) {
return src.toString();
}
@Override
public UUID fromString(String src) {
return UUID.fromString(src);
}
@Override
public Class<UUID> getTypeClass() {
return UUID.class;
}
}
Every adapter must implement three things:
getTypeClass()— returns the Java class this adapter handles.- A
tomethod — converts from your Java type to the LH type (here,toString(UUID)). - A
frommethod — converts from the LH type back to your Java type (here,fromString(String)).
Step 2: Register the Adapter
Config-Level Registration
Register your adapter on LHConfig before creating any LHTaskWorker instances:
LHConfig config = new LHConfig();
config.registerTypeAdapter(new UUIDAdapter());
Each Java class can only have one adapter registered. Attempting to register a second adapter for the same class will throw an IllegalArgumentException.
Workflow-Level Registration
You can also register adapters directly on a Workflow instance if you need the adapter for workflow-level operations:
Workflow workflow = new WorkflowImpl("my-workflow", wf -> {
// workflow definition
});
workflow.registerTypeAdapter(new UUIDAdapter());
Workflow's do not inherit adapters from LHConfig. If you register an adapter on LHConfig, you must also register it on each Workflow that needs it.
Step 3: Use the Adapted Type in Task Methods
With the adapter registered, you can use UUID directly in your @LHTaskMethod signatures:
import io.littlehorse.sdk.worker.LHTaskMethod;
import io.littlehorse.sdk.worker.WorkerContext;
import java.util.UUID;
public class Worker {
@LHTaskMethod(value = "get-uuid", description = "Generates and returns a random UUID.")
public UUID getUUID() {
return UUID.randomUUID();
}
@LHTaskMethod(value = "echo-uuid", description = "Receives a UUID and logs it.")
public void echoUUID(UUID uuid, WorkerContext context) {
context.log("Received UUID via adapter: " + uuid);
}
}
The SDK automatically:
- Registers the
get-uuidTaskDef with a return type ofSTR. - Registers the
echo-uuidTaskDef with one input parameter of typeSTR. - Converts the
UUIDreturn value to aStringwhen sending task output. - Converts the incoming
Stringvalue to aUUIDwhen providing task input.
Step 4: Define the Workflow
The workflow uses standard STR variables—the adapter handles the conversion transparently:
import io.littlehorse.sdk.wfsdk.WfRunVariable;
import io.littlehorse.sdk.wfsdk.Workflow;
import io.littlehorse.sdk.wfsdk.internal.WorkflowImpl;
public static Workflow getWorkflow() {
return new WorkflowImpl("example-type-adapter", wf -> {
WfRunVariable uuidVar = wf.declareStr("uuid").searchable();
uuidVar.assign(wf.execute("get-uuid"));
wf.execute("echo-uuid", uuidVar);
});
}
Step 5: Put It All Together
Here is a complete example that registers the adapter, creates workers, and starts the workflow:
import io.littlehorse.sdk.common.config.LHConfig;
import io.littlehorse.sdk.wfsdk.Workflow;
import io.littlehorse.sdk.worker.LHTaskWorker;
import io.littlehorse.sdk.worker.adapter.LHStringAdapter;
import java.util.UUID;
public class TypeAdapterExample {
public static void main(String[] args) throws Exception {
LHConfig config = new LHConfig();
// Register the UUID adapter
config.registerTypeAdapter(new LHStringAdapter<UUID>() {
@Override
public String toString(UUID src) {
return src.toString();
}
@Override
public UUID fromString(String src) {
return UUID.fromString(src);
}
@Override
public Class<UUID> getTypeClass() {
return UUID.class;
}
});
// Create workers
Worker executable = new Worker();
LHTaskWorker getUuidWorker = new LHTaskWorker(executable, "get-uuid", config);
LHTaskWorker echoUuidWorker = new LHTaskWorker(executable, "echo-uuid", config);
// Register TaskDefs
getUuidWorker.registerTaskDef();
echoUuidWorker.registerTaskDef();
// Register WfSpec
Workflow workflow = getWorkflow();
workflow.registerWfSpec(config.getBlockingStub());
// Start workers
getUuidWorker.start();
echoUuidWorker.start();
}
}
StructDef Integration
Type Adapters integrate with @LHStructDef-annotated classes. If a Struct field's Java type has a registered adapter, the SDK automatically uses the adapter during serialization and deserialization of that field. The StructDef schema reflects the adapter's variable type, not the raw Java type.
For example, consider a StructDef with a UUID field and the above UUIDAdapter registered:
import io.littlehorse.sdk.worker.LHStructDef;
import java.util.UUID;
@LHStructDef(name = "order")
public class Order {
private UUID orderId;
private String customerName;
private double total;
public Order() {}
public UUID getOrderId() { return orderId; }
public void setOrderId(UUID orderId) { this.orderId = orderId; }
public String getCustomerName() { return customerName; }
public void setCustomerName(String customerName) { this.customerName = customerName; }
public double getTotal() { return total; }
public void setTotal(double total) { this.total = total; }
}
When this StructDef is registered, the orderId field is described as type STR (not UUID), because the built-in UUID adapter maps UUID → STR. At runtime, the SDK converts between UUID and String automatically when serializing and deserializing the Struct.
Validation
The SDK validates adapter compatibility at worker startup. When an LHTaskWorker starts, it checks that each adapted parameter's VariableType matches the corresponding field in the TaskDef schema. If there is a mismatch, the worker will report a validation error:
TaskDef provides INT, but adapter for java.util.UUID maps to STR
This ensures that your adapters and TaskDef schemas stay in sync.
Writing Adapters for Other Types
Here are a few more examples to illustrate how to write adapters for different scenarios.
An Enum Adapter (String-based)
public class StatusAdapter implements LHStringAdapter<OrderStatus> {
@Override
public String toString(OrderStatus src) {
return src.name();
}
@Override
public OrderStatus fromString(String src) {
return OrderStatus.valueOf(src);
}
@Override
public Class<OrderStatus> getTypeClass() {
return OrderStatus.class;
}
}
A Timestamp Adapter
import io.littlehorse.sdk.worker.adapter.LHTimestampAdapter;
import com.google.protobuf.Timestamp;
import java.time.ZonedDateTime;
import java.time.ZoneOffset;
public class ZonedDateTimeAdapter implements LHTimestampAdapter<ZonedDateTime> {
@Override
public Timestamp toTimestamp(ZonedDateTime src) {
long epochSeconds = src.toEpochSecond();
return Timestamp.newBuilder()
.setSeconds(epochSeconds)
.setNanos(src.getNano())
.build();
}
@Override
public ZonedDateTime fromTimestamp(Timestamp src) {
return ZonedDateTime.ofInstant(
java.time.Instant.ofEpochSecond(src.getSeconds(), src.getNanos()),
ZoneOffset.UTC);
}
@Override
public Class<ZonedDateTime> getTypeClass() {
return ZonedDateTime.class;
}
}
Further Resources
- Type Adapter Example — A runnable example demonstrating a UUID adapter end-to-end.
- StructDefs and Structs — Learn how structured types work in LittleHorse.
- Task Worker Development — General guide to developing task workers.
- Variables — Overview of LittleHorse variable types.