4. Examples
For the actual code and instructions on how to execute them, please check the Java samples in our dedicated Samples repository.
4.1. Sample Overview
Sample |
Description |
Key concept illustrated |
|---|---|---|
|
Submit a single task with inline input |
Basic task submission |
|
Reuse one blob across multiple tasks |
Blob reuse |
|
Chain tasks using outputs as inputs |
Task graph via blobs |
|
Load a library JAR at runtime |
Dynamic worker loading |
4.2. Task Graphs via Blobs
The blob abstraction is the mechanism that enables directed acyclic graphs (DAGs) of tasks in ArmoniK. The key insight is that an OutputBlobDefinition does not hold data — it is a named placeholder for a result that does not exist yet. When that placeholder is given to a subsequent task as an input, ArmoniK records the dependency and will not schedule the consuming task until the producing task has written to that blob.
4.2.1. How it works
Submit task A, declaring an output blob
result_a.Use the
BlobHandleforresult_aas the input of task B.Submit task B — it will only run once task A has completed and written
result_a.
Task A ──writes──► blob result_a ──read by──► Task B
The control plane enforces this ordering automatically. No polling or manual synchronisation is needed on the client side.
4.2.2. TaskDependencies example
The TaskDependencies sample illustrates a simple fan-in graph:
task1 (1+2=3) ──┐
├──► task3 (3+7=10)
task2 (3+4=7) ──┘
The client submits all three tasks upfront. Tasks 1 and 2 are independent; task 3 declares the outputs of tasks 1 and 2 as its inputs, so the control plane holds task 3 until both predecessors complete.
On the client side, this is expressed by passing the BlobHandle of a previous task’s output directly into the next TaskDefinition:
// Task 1 — independent
var taskDef1 = new TaskDefinition()
.withInput("num1", InputBlobDefinition.from("1".getBytes(UTF_8)))
.withInput("num2", InputBlobDefinition.from("2".getBytes(UTF_8)))
.withOutput("result");
var task1 = session.submitTask(taskDef1);
// Task 2 — independent
var taskDef2 = new TaskDefinition()
.withInput("num1", InputBlobDefinition.from("3".getBytes(UTF_8)))
.withInput("num2", InputBlobDefinition.from("4".getBytes(UTF_8)))
.withOutput("result");
var task2 = session.submitTask(taskDef2);
// Task 3 — depends on outputs of task 1 and task 2
var taskDef3 = new TaskDefinition()
.withInput("num1", task1.getOutput("result")) // BlobHandle from task 1
.withInput("num2", task2.getOutput("result")) // BlobHandle from task 2
.withOutput("result");
session.submitTask(taskDef3);
task1.getOutput("result") returns a BlobHandle — a reference to an output blob that has been declared but not yet written. Passing it as input to task 3 is what expresses the data dependency to the control plane.
4.2.4. Dynamic graphs from workers
Task graphs are not limited to what the client defines upfront. A worker can itself call context.submitTask(...), passing one of its own outputs as the input of a new child task. This enables workflows whose shape is determined at runtime — for example, a recursive divide-and-conquer where each task spawns sub-tasks only after inspecting its input.
See Worker — Subtask submission for the worker-side API.
4.3. Result Retrieval
Output blobs are surfaced through the BlobCompletionListener registered on the session. When any output blob transitions to COMPLETED or ABORTED, the listener is called asynchronously:
public class SimpleBlobListener implements BlobCompletionListener {
@Override
public void onBlobSuccess(Blob blob) {
System.out.println("Data: " + new String(blob.data(), UTF_8));
}
@Override
public void onBlobError(BlobError error) {
System.out.println("Error: " + error.cause().getMessage());
}
}
In a graph workflow, each leaf output blob fires a onBlobSuccess callback independently as soon as the task that produces it finishes, even if other branches of the graph are still running.