Java SDK
The Conductor Java SDK lets you write workers, define workflows as code, and manage workflow executions from your Java application.
- Java 17 or above
- Gradle or Maven for dependency management
- A Conductor server to connect to. For quick testing, sign up for free Orkes Developer Edition.
Install the SDK
Add the following dependencies to your project:
- Gradle
- Maven
dependencies {
implementation 'org.conductoross:conductor-client:5.0.1'
implementation 'org.conductoross:java-sdk:5.0.1'
implementation 'io.orkes.conductor:orkes-conductor-client:5.0.1'
}
<dependency>
<groupId>org.conductoross</groupId>
<artifactId>conductor-client</artifactId>
<version>5.0.1</version>
</dependency>
<dependency>
<groupId>org.conductoross</groupId>
<artifactId>java-sdk</artifactId>
<version>5.0.1</version>
</dependency>
<dependency>
<groupId>io.orkes.conductor</groupId>
<artifactId>orkes-conductor-client</artifactId>
<version>5.0.1</version>
</dependency>
Optionally, you can also add spring module for auto configuration:
<dependency>
<groupId>org.conductoross</groupId>
<artifactId>conductor-client-spring</artifactId>
<version>5.0.1</version>
</dependency>
Connect to Conductor
In Orkes Conductor, an application represents your SDK client and controls what it can access on the cluster. Each application has access keys (a key ID and secret) that the SDK uses to authenticate your requests.
To create an application:
- Go to Access Control > Applications from the left menu on your Conductor cluster.
- Select + Create application.
- Enter the application name.
- Select Save.
To retrieve the access key:
- Open the application.
- In Application roles, enable the Worker role.
- In the Access Keys section, select + Create access key to generate a unique Key ID, Key Secret, and Server URL.
The Key Secret is shown only once. So ensure to copy and store it securely.
Set environment variables
The SDK reads your server URL and credentials from environment variables. To make them available in every terminal session, add them to your shell profile (~/.zshrc or ~/.bash_profile):
export CONDUCTOR_SERVER_URL=https://SERVER_URL/api
export CONDUCTOR_AUTH_KEY=your-key-id
export CONDUCTOR_AUTH_SECRET=your-key-secret
Reload your shell profile after adding:
source ~/.zshrc
If you set environment variables using export in a terminal, they only persist for that session. Any new terminal will require you to export them again, which is a common source of connection errors when running workers and workflows in separate terminals.
Initialize the SDK client
Every application that uses the Conductor Java SDK starts with the same setup:
import io.orkes.conductor.client.ApiClient;
import com.netflix.conductor.client.http.TaskClient;
import com.netflix.conductor.client.http.WorkflowClient;
import com.netflix.conductor.sdk.workflow.executor.WorkflowExecutor;
ApiClient client = new ApiClient(
System.getenv("CONDUCTOR_SERVER_URL"),
System.getenv("CONDUCTOR_AUTH_KEY"),
System.getenv("CONDUCTOR_AUTH_SECRET")
);
ApiClient handles authentication against Orkes Conductor. All other clients are built on top of it:
WorkflowExecutor executor = new WorkflowExecutor(client, 10); // register and run workflows
WorkflowClient workflowClient = new WorkflowClient(client); // pause, resume, terminate, restart
TaskClient taskClient = new TaskClient(client); // start workers
Quickstart
This tutorial walks you through creating a workflow with a single worker task and running it end-to-end.
Step 1: Set up the project
Create a new folder:
mkdir conductor-app
cd conductor-app
mkdir -p src/main/java
Create settings.gradle in the root folder:
rootProject.name = 'conductor-app'
Create build.gradle in the root folder:
plugins {
id 'java'
id 'application'
}
application {
mainClass = 'Main'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.conductoross:conductor-client:5.0.1'
implementation 'org.conductoross:java-sdk:5.0.1'
implementation 'io.orkes.conductor:orkes-conductor-client:5.0.1'
runtimeOnly 'org.slf4j:slf4j-nop:2.0.9'
}
Generate the Gradle wrapper by running the following command:
gradle wrapper
Step 2: Set environment variables
export CONDUCTOR_SERVER_URL=https://SERVER_URL/api
export CONDUCTOR_AUTH_KEY=your-key-id
export CONDUCTOR_AUTH_SECRET=your-key-secret
Add these to your shell profile (~/.zshrc or ~/.bashrc) so they persist across terminal sessions.
Step 3: Create the files
Write your worker file. Create src/main/java/GreetWorker.java:
import com.netflix.conductor.client.worker.Worker;
import com.netflix.conductor.common.metadata.tasks.Task;
import com.netflix.conductor.common.metadata.tasks.TaskResult;
public class GreetWorker implements Worker {
@Override
public String getTaskDefName() {
return "greet";
}
@Override
public TaskResult execute(Task task) {
String name = (String) task.getInputData().get("name");
TaskResult result = new TaskResult(task);
result.setStatus(TaskResult.Status.COMPLETED);
result.addOutputData("greeting", "Hello, " + name + "!");
return result;
}
}
Next, run your first workflow app. Create src/main/java/Main.java:
import com.netflix.conductor.client.automator.TaskRunnerConfigurer;
import com.netflix.conductor.client.http.TaskClient;
import com.netflix.conductor.client.http.WorkflowClient;
import com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;
import com.netflix.conductor.common.run.Workflow;
import com.netflix.conductor.sdk.workflow.def.ConductorWorkflow;
import com.netflix.conductor.sdk.workflow.def.tasks.SimpleTask;
import com.netflix.conductor.sdk.workflow.executor.WorkflowExecutor;
import io.orkes.conductor.client.ApiClient;
import java.util.List;
import java.util.Map;
public class Main {
public static void main(String[] args) throws Exception {
ApiClient client = new ApiClient(
System.getenv("CONDUCTOR_SERVER_URL"),
System.getenv("CONDUCTOR_AUTH_KEY"),
System.getenv("CONDUCTOR_AUTH_SECRET")
);
WorkflowExecutor executor = new WorkflowExecutor(client, 10);
WorkflowClient workflowClient = new WorkflowClient(client);
TaskClient taskClient = new TaskClient(client);
// Define and register the workflow
ConductorWorkflow<Map> workflow = new ConductorWorkflow<>(executor);
workflow.setName("greetings");
workflow.setVersion(1);
SimpleTask greetTask = new SimpleTask("greet", "greet_ref");
greetTask.input("name", "${workflow.input.name}");
workflow.add(greetTask);
workflow.registerWorkflow(true, true);
// Start worker
TaskRunnerConfigurer configurer = new TaskRunnerConfigurer.Builder(
taskClient,
List.of(new GreetWorker())
).withThreadCount(10).build();
configurer.init();
// Run the workflow
StartWorkflowRequest request = new StartWorkflowRequest();
request.setName("greetings");
request.setVersion(1);
request.setInput(Map.of("name", "Conductor"));
String workflowId = workflowClient.startWorkflow(request);
Workflow run = null;
for (int i = 0; i < 30; i++) {
run = workflowClient.getWorkflow(workflowId, true);
if (run.getStatus() == Workflow.WorkflowStatus.COMPLETED) break;
Thread.sleep(1000);
}
System.out.println("Result: " + run.getOutput().get("greeting"));
System.out.println("Execution: " + System.getenv("CONDUCTOR_SERVER_URL")
.replace("/api", "") + "/execution/" + workflowId);
configurer.shutdown();
}
}
Step 4: Run it
./gradlew run
Expected output:
Result: Hello, Conductor!
Execution: https://SERVER_URL/execution/<workflow-id>
Open the execution URL to view the workflow run in the Conductor UI. Select the greet_ref task, then go to the Output tab to confirm the greeting is displayed.

This also registers the task and workflow definitions on your Conductor cluster. To verify, go to Definitions > Task, and confirm that the greet task is created.

Similarly, the greetings workflow is available under Definitions > Workflow.

That's it. You have successfully run a simple workflow. The following sections cover each concept in detail.
Workers
A worker is a piece of code responsible for executing a task. In Conductor, workers can be implemented in any language and deployed anywhere.
Implement a worker
In Java, a worker is a method annotated with @WorkerTask. The annotation registers the method as the handler for a task type.
import com.netflix.conductor.sdk.workflow.task.InputParam;
import com.netflix.conductor.sdk.workflow.task.WorkerTask;
public class ConductorWorkers {
@WorkerTask("greet")
public @OutputParam("greeting") String greet(@InputParam("name") String name) {
return "Hello, " + name + "!";
}
}
@InputParam maps a task input field to a method parameter. @OutputParam sets the key used for the return value in the task output.
Workers can accept and return complex types:
@WorkerTask("get_insurance_quote")
public InsuranceQuote getInsuranceQuote(GetInsuranceQuote quoteInput) {
InsuranceQuote quote = new InsuranceQuote();
// implementation
return quote;
}
Conductor serializes and deserializes inputs and outputs as JSON, so any type you use must be JSON-serializable.
Alternatively, implement the Worker interface directly for full control over task execution:
import com.netflix.conductor.client.worker.Worker;
import com.netflix.conductor.common.metadata.tasks.Task;
import com.netflix.conductor.common.metadata.tasks.TaskResult;
public class GreetWorker implements Worker {
@Override
public String getTaskDefName() {
return "greet";
}
@Override
public TaskResult execute(Task task) {
String name = (String) task.getInputData().get("name");
TaskResult result = new TaskResult(task);
result.setStatus(TaskResult.Status.COMPLETED);
result.addOutputData("greeting", "Hello, " + name + "!");
return result;
}
}
Use @WorkerTask for most cases; it's cleaner and handles input/output mapping automatically. Use the Worker interface when you need direct access to the Task object or custom error handling.
Start and stop workers
Workers use a long-poll mechanism to check for tasks. TaskRunnerConfigurer manages worker startup and shutdown:
import com.netflix.conductor.client.automator.TaskRunnerConfigurer;
TaskRunnerConfigurer configurer = new TaskRunnerConfigurer.Builder(
taskClient,
List.of(new ConductorWorkers())
).withThreadCount(5).build();
configurer.init();
// ... application runs ...
configurer.shutdown();
To discover workers by package scanning instead of listing them explicitly, use WorkflowExecutor:
WorkflowExecutor executor = new WorkflowExecutor(client, 10);
executor.initWorkers("com.mycompany.workers"); // scans this package for @WorkerTask methods
initWorkers scans the classpath for @WorkerTask methods in the specified packages. The worker methods must be public and their containing class must have a no-args constructor.
Concurrency with withThreadCount
withThreadCount controls how many tasks the worker executes concurrently (default: 1). Leaving it at 1 is a common scaling bottleneck.
new TaskRunnerConfigurer.Builder(taskClient, List.of(new ConductorWorkers()))
.withThreadCount(10)
.build();
Recommended values:
- CPU-bound tasks: 1–4 (matches core count)
- I/O-bound tasks: 10–50 (threads spend most time waiting)
Worker design principles
- Workers are stateless and contain no workflow-specific logic.
- Each worker executes one task and produces a defined output for a given input.
- Workers should be idempotent; a task may be rescheduled if it times out.
- Retry and timeout logic is handled by the Conductor server, not the worker.
Workflows
A workflow is the blueprint that connects tasks into a sequence. It defines which tasks run, in what order, and how outputs from one task become inputs to the next. Workflows handle branching, parallelism, retries, and timeouts; all configured in the workflow definition, not in your worker code.
You can define workflows in the Conductor UI, via the API, or in code using the SDK.
Define a workflow as code
Use ConductorWorkflow to define workflows in Java. Tasks are added in execution order using .add():
import com.netflix.conductor.sdk.workflow.def.ConductorWorkflow;
import com.netflix.conductor.sdk.workflow.def.tasks.SimpleTask;
ConductorWorkflow<Map> workflow = new ConductorWorkflow<>(executor);
workflow.setName("email_workflow");
workflow.setVersion(1);
SimpleTask getUserEmail = new SimpleTask("get_user_email", "get_user_email_ref");
getUserEmail.input("userId", "${workflow.input.userId}");
SimpleTask sendEmail = new SimpleTask("send_email", "send_email_ref");
sendEmail.input("email", "${get_user_email_ref.output.result}");
sendEmail.input("subject", "Hello from Orkes");
sendEmail.input("body", "Welcome!");
workflow.add(getUserEmail);
workflow.add(sendEmail);
workflow.registerWorkflow(true, true);
Use system tasks
System tasks are pre-built tasks available in Conductor without writing a worker.
Wait task: Pauses the workflow until a certain timestamp, duration, or an external signal is received.
import com.netflix.conductor.sdk.workflow.def.tasks.Wait;
import java.time.Duration;
import java.time.ZonedDateTime;
// Wait for a fixed duration
Wait waitTwoSec = new Wait("wait_2_sec", Duration.ofSeconds(2));
// Wait until a specific time
ZonedDateTime until = ZonedDateTime.parse("2030-01-31T00:00:00+00:00[UTC]");
Wait waitTillDate = new Wait("wait_till_date", until);
// Wait for an external signal
Wait waitForSignal = new Wait("wait_for_signal");
workflow.add(waitTwoSec);
HTTP task: The HTTP task is used to make calls to remote services exposed over HTTP/HTTPS.
import com.netflix.conductor.sdk.workflow.def.tasks.Http;
Http httpCall = new Http("call_api");
httpCall.url("https://orkes-api-tester.orkesconductor.com/api");
httpCall.method("GET");
workflow.add(httpCall);
Inline task: Runs ECMA-compliant JavaScript inline.
import com.netflix.conductor.sdk.workflow.def.tasks.JavaScript;
JavaScript jsTask = new JavaScript("hello_script",
"""
function greetings() {
return { "text": "hello " + $.name }
}
greetings();
"""
);
workflow.add(jsTask);
JSON JQ Transform task: Transforms JSON using jq expressions.
import com.netflix.conductor.sdk.workflow.def.tasks.JQ;
JQ jqTask = new JQ("jq_process", "{ key3: (.key1.value1 + .key2.value2) }");
workflow.add(jqTask);
Learn more about other task types.
Execute a workflow
Asynchronously: Use when workflows are long-running.
import com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;
StartWorkflowRequest request = new StartWorkflowRequest();
request.setName("greetings");
request.setVersion(1);
request.setInput(Map.of("name", "Orkes"));
String workflowId = workflowClient.startWorkflow(request);
Synchronously: Use when workflows complete quickly. executeDynamic sends the workflow definition with the request and waits for completion.
CompletableFuture<Workflow> future = workflow.executeDynamic(input);
Workflow result = future.get(30, TimeUnit.SECONDS);
System.out.println("Output: " + result.getOutput());
Manage workflow executions
Get WorkflowClient from ApiClient:
WorkflowClient workflowClient = new WorkflowClient(client);
Get execution status
Retrieves the status of a workflow execution. When includeTaskDetails is true, the response includes all completed and in-progress tasks.
Workflow wf = workflowClient.getWorkflow(workflowId, true);
Pause and resume
A paused workflow lets currently running tasks complete but does not schedule new tasks until resumed.
workflowClient.pauseWorkflow(workflowId);
workflowClient.resumeWorkflow(workflowId);
Terminate
Stops the workflow immediately and moves it to TERMINATED state.
workflowClient.terminateWorkflow(workflowId, "Cancelled by user");
Retry a failed workflow
Resumes the workflow from the failed task without restarting from the beginning.
workflowClient.retryWorkflow(workflowId);
Restart a workflow
Restarts a workflow in a terminal state (COMPLETED, TERMINATED, FAILED) from the beginning.
workflowClient.restartWorkflow(workflowId, false); // set true to use the latest workflow definition
Rerun from a specific task
Resumes the workflow from a specific task, re-executing it and all subsequent tasks.
import com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest;
RerunWorkflowRequest rerunRequest = new RerunWorkflowRequest();
rerunRequest.setReRunFromTaskId("task_id_to_rerun_from");
workflowClient.rerunWorkflow(workflowId, rerunRequest);
Search executions
Queries workflow executions by status, type, or other fields.
workflowClient.search(0, 10, "", "*", "workflowType=\"greetings\" AND status=\"FAILED\"");
Supported query fields: status, correlationId, workflowType, version, startTime.
Test workflows
The Conductor server provides a test endpoint (POST /api/workflow/test) that lets you run a workflow with mocked task outputs, no workers required.
Set up the test server
// @BeforeClass in JUnit — called once per test lifecycle
WorkflowTestRunner testRunner = new WorkflowTestRunner(8096, "3.5.2");
testRunner.init("com.mycompany.workers"); // package containing your @WorkerTask methods
WorkflowExecutor executor = testRunner.getWorkflowExecutor();
Clean up after tests:
// @AfterClass in JUnit
testRunner.shutdown();
Write a test
import static org.junit.Assert.*;
GetInsuranceQuote input = new GetInsuranceQuote();
input.setName("personA");
input.setAmount(1000000.0);
input.setZipCode("10121");
CompletableFuture<Workflow> workflowFuture = executor.executeWorkflow("InsuranceQuoteWorkflow", 1, input);
Workflow workflow = workflowFuture.get();
assertEquals(Workflow.WorkflowStatus.COMPLETED, workflow.getStatus());
assertNotNull(workflow.getOutput());
assertTrue(workflow.getTasks().stream().anyMatch(t -> t.getTaskDefName().equals("get_insurance_quote")));
Workers annotated with @WorkerTask are regular Java methods and can be unit tested independently with any testing framework.
Next steps
- Examples: Browse the full examples on GitHub.