Skip to main content
Version: Next

Arrays

LittleHorse provides a native ARRAY variable type that gives you strongly-typed, ordered lists with per-element type enforcement.

Use ARRAY when you want compile-time and runtime guarantees that every element in your list conforms to a specific type. Use JSON_ARR when you need an untyped or heterogeneous list, or for backward compatibility with existing workflows.

Supported Element Types

An ARRAY can hold elements of any TypeDefinition:

  • Primitives: INT, DOUBLE, STR, BOOL, BYTES, etc...
  • Complex types: STRUCT (backed by a registered StructDef)
  • Nested arrays: ARRAY (arrays of arrays)

Input Validation

The LittleHorse server validates array inputs at ingress (e.g., when running a workflow or returning from a task). If an element does not match the Array's declared type, the server rejects the request with INVALID_ARGUMENT.

In Practice

To use an ARRAY variable in LittleHorse:

  1. Declare an ARRAY variable in your WfSpec using declareArray().
  2. Produce and consume arrays in your task workers, marking them as distinct ARRAYs instead of JSON_ARR using the @LHType(isLHArray = true) annotation.
  3. Use mutation operations to manipulate array contents.

Declaring an ARRAY Variable

Use the declareArray method on WorkflowThread to create a typed array variable. You specify the element type as a class.

import io.littlehorse.sdk.wfsdk.WfRunVariable;
import io.littlehorse.sdk.wfsdk.WorkflowThread;

public void wfLogic(WorkflowThread wf) {
// Declare an array of Long elements
WfRunVariable longArray = wf.declareArray("my-numbers", Long.class);

// Declare an array of String elements
WfRunVariable stringArray = wf.declareArray("my-strings", String.class);
}

Task Workers with Native Arrays

Task workers can produce and consume native arrays using the @LHType(isLHArray = true) annotation. Without the annotation, Java arrays would default to JSON_ARR serialization. This is done for backwards compatibility purposes, since old clients automatically treated Java arrays as JSON arrays.

Producing an Array

Annotate the task method with @LHType(isLHArray = true) and return a Java array:

import io.littlehorse.sdk.worker.LHTaskMethod;
import io.littlehorse.sdk.worker.LHType;

public class ArrayWorker {

@LHTaskMethod("produce-array")
@LHType(isLHArray = true)
public Long[] produceArray() {
return new Long[] {1L, 2L, 3L};
}
}

Consuming an Array

Annotate a method parameter with @LHType(isLHArray = true) and accept a Java array:

import io.littlehorse.sdk.worker.LHTaskMethod;
import io.littlehorse.sdk.worker.LHType;

public class ArrayWorker {

@LHTaskMethod("consume-array")
public String consumeArray(@LHType(isLHArray = true) Long[] arr) {
return "Received " + arr.length + " elements";
}
}

Arrays in Struct Fields

For struct properties that are arrays, use the @LHStructField(isLHArray = true) annotation on the getter or field:

import io.littlehorse.sdk.worker.LHStructDef;
import io.littlehorse.sdk.worker.LHStructField;

@LHStructDef("shopping-cart")
public class ShoppingCart {
@LHStructField(isLHArray = true)
private Long[] itemIds;

public Long[] getItemIds() {
return itemIds;
}

public void setItemIds(Long[] itemIds) {
this.itemIds = itemIds;
}
}

Mutation Operations

The ARRAY type supports the following mutation operations:

OperationDescription
ASSIGNAssign an array value to the variable
EXTENDAppend a single element to the array (type-checked against the element type)
REMOVE_IF_PRESENTRemove all occurrences of a value from the array
REMOVE_INDEXRemove the element at a specific index
import io.littlehorse.sdk.wfsdk.NodeOutput;
import io.littlehorse.sdk.wfsdk.WfRunVariable;
import io.littlehorse.sdk.wfsdk.WorkflowThread;

public void wfLogic(WorkflowThread wf) {
WfRunVariable arrVar = wf.declareArray("my-array", Long.class);

// Assign the result of a task to the array
NodeOutput produced = wf.execute("produce-array");
arrVar.assign(produced);

// Append a single element
arrVar.assign(arrVar.extend(4L));

// Remove all occurrences of a value
arrVar.assign(arrVar.removeIfPresent(2L));

// Remove the element at index 1
arrVar.assign(arrVar.removeIndex(1));
}

Accessing Array Elements

You can use index-based access to read individual elements from an ARRAY variable using the .get() method with a numeric index:

public void wfLogic(WorkflowThread wf) {
WfRunVariable arrVar = wf.declareArray("my-array", Long.class);

// Access the element at index 0
wf.execute("process-item", arrVar.get(0));

// Access the element at index 2
wf.execute("process-item", arrVar.get(2));
}

Conditionals with IN and NOT_IN

The doesContain and doesNotContain methods work with native ARRAY variables:

import io.littlehorse.sdk.wfsdk.WfRunVariable;
import io.littlehorse.sdk.wfsdk.WorkflowThread;

public void wfLogic(WorkflowThread wf) {
WfRunVariable arrVar = wf.declareArray("my-array", Long.class);
WfRunVariable valueToCheck = wf.declareInt("value-to-check");

wf.doIfElse(
arrVar.doesContain(valueToCheck),
ifBody -> {
ifBody.execute("handle-found", valueToCheck);
},
elseBody -> {
elseBody.execute("handle-not-found", valueToCheck);
}
);
}

Spawning Threads per Element

The spawnThreadForEach method works with native ARRAY variables, spawning one child thread per element in the array. Each child thread receives the corresponding element as input.

import io.littlehorse.sdk.wfsdk.SpawnedThreads;
import io.littlehorse.sdk.wfsdk.WfRunVariable;
import io.littlehorse.sdk.wfsdk.WorkflowThread;

public void wfLogic(WorkflowThread wf) {
WfRunVariable arrVar = wf.declareArray("my-array", Long.class);

SpawnedThreads threads = wf.spawnThreadForEach(
arrVar,
"process-element",
this::processElementThread,
null
);

wf.waitForThreads(threads);
}

public void processElementThread(WorkflowThread wf) {
WfRunVariable input = wf.declareInt("INPUT").required();
wf.execute("process-item", input);
}

Full Example

A complete working example is available in the LittleHorse examples repository.

Further Resources