Skip to main content
Version: 1.0

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.

info

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 InterfaceLH Variable TypeConversion Methods
LHStringAdapter<T>STRtoString(T) / fromString(String)
LHLongAdapter<T>INTtoLong(T) / fromLong(Long)
LHIntegerAdapter<T>INTtoInteger(T) / fromInteger(Integer)
LHDoubleAdapter<T>DOUBLEtoDouble(T) / fromDouble(Double)
LHBooleanAdapter<T>BOOLtoBoolean(T) / fromBoolean(Boolean)
LHBytesAdapter<T>BYTEStoBytes(T) / fromBytes(byte[])
LHTimestampAdapter<T>TIMESTAMPtoTimestamp(T) / fromTimestamp(Timestamp)
LHJsonObjAdapter<T>JSON_OBJtoJsonObj(T) / fromJsonObj(Object)
LHJsonArrAdapter<T>JSON_ARRtoJsonArr(T) / fromJsonArr(List)
LHWfRunIdAdapter<T>WF_RUN_IDtoWfRunId(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.common.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 to method — converts from your Java type to the LH type (here, toString(UUID)).
  • A from method — converts from the LH type back to your Java type (here, fromString(String)).

Step 2: Register the Adapter

Registration

Register your adapter when building your LHConfig:

LHConfig config = LHConfig.newBuilder()
.loadFromProperties(props)
.addTypeAdapter(new UUIDAdapter())
.build();

LHTypeAdapters registered on LHConfig are available to all workers and workflows that use that config:

If you want to use LHTypeAdapters in your LHTaskWorker, you can do so by passing your LHConfig into the worker's constructor:

LHTaskWorker worker = new LHTaskWorker(executable, "task-name", config)

Use with Task Methods

With the adapter registered, you can use UUID directly in your @LHTaskMethod signatures:

type-adapter/src/main/java/io/littlehorse/examples/Worker.java
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);
}
}

Now, register your Task Workers with the same LHConfig that contains your adapter:

type-adapter/src/main/java/io/littlehorse/examples/RunWorkers.java
package io.littlehorse.examples;

import io.littlehorse.sdk.common.config.LHConfig;
import io.littlehorse.sdk.wfsdk.WfRunVariable;
import io.littlehorse.sdk.wfsdk.Workflow;
import io.littlehorse.sdk.wfsdk.internal.WorkflowImpl;
import io.littlehorse.sdk.worker.LHTaskWorker;
import java.io.IOException;

public class RunWorkers {
public static void main(String[] args) throws IOException {
LHConfig config = LHConfig.newBuilder()
.addTypeAdapter(new UUIDTypeAdapter())
.build();

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();

// Start workers
getUuidWorker.start();
echoUuidWorker.start();
}
}

In the above code, the SDK automatically:

  • Registers the get-uuid TaskDef with a return type of STR.
  • Registers the echo-uuid TaskDef with one input parameter of type STR.
  • Converts the UUID return value to a String when sending task output.
  • Converts the incoming String value to a UUID when providing task input.

Now you can pass STR values to your tasks and they will be automatically converted to UUID by the SDK.

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.common.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