Temporal Nexus - Java SDK Feature Guide
Temporal Java SDK support for Nexus is now Generally Available.
Use Temporal Nexus to connect Temporal Applications within and across Namespaces using a Nexus Endpoint, a Nexus Service contract, and Nexus Operations.
This page shows how to do the following:
- Run a development Temporal Service with Nexus enabled
- Create caller and handler Namespaces
- Create a Nexus Endpoint to route requests from caller to handler
- Define the Nexus Service contract
- Develop a Nexus Service and Operation handlers
- Develop a caller Workflow that uses a Nexus Service
- Make Nexus calls across Namespaces with a development Server
- Make Nexus calls across Namespaces in Temporal Cloud
This documentation uses source code derived from the Java Nexus sample.
Run the Temporal Development Server with Nexus enabled
Prerequisites:
- Install the latest Temporal CLI (v1.3.0 or higher recommended)
- Install the latest Temporal Java SDK (v1.28.0 or higher recommended)
The first step in working with Temporal Nexus involves starting a Temporal server with Nexus enabled.
temporal server start-dev
This command automatically starts the Temporal development server with the Web UI, and creates the default
Namespace. It uses an in-memory database, so do not use it for real use cases.
The Temporal Web UI should now be accessible at http://localhost:8233, and the Temporal Server should now be available for client connections on localhost:7233
.
Create caller and handler Namespaces
Before setting up Nexus endpoints, create separate Namespaces for the caller and handler.
temporal operator namespace create --namespace my-target-namespace
temporal operator namespace create --namespace my-caller-namespace
my-target-namespace
will contain the Nexus Operation handler, and we will use a Workflow in my-caller-namespace
to call that Operation handler.
We use different namespaces to demonstrate cross-Namespace Nexus calls.
Create a Nexus Endpoint to route requests from caller to handler
After establishing caller and handler Namespaces, the next step is to create a Nexus Endpoint to route requests.
temporal operator nexus endpoint create \
--name my-nexus-endpoint-name \
--target-namespace my-target-namespace \
--target-task-queue my-handler-task-queue
You can also use the Web UI to create the Namespaces and Nexus endpoint.
Define the Nexus Service contract
Defining a clear contract for the Nexus Service is crucial for smooth communication.
In this example, there is a service package that describes the Service and Operation names along with input/output types for caller Workflows to use the Nexus Endpoint.
Each Temporal SDK includes and uses a default Data Converter. The default data converter encodes payloads in the following order: Null, Byte array, Protobuf JSON, and JSON. In a polyglot environment, that is where more than one language and SDK is being used to develop a Temporal solution, Protobuf and JSON are common choices. This example uses Java classes serialized into JSON.
core/src/main/java/io/temporal/samples/nexus/service/NexusService.java
@Service
public interface NexusService {
enum Language {
EN,
FR,
DE,
ES,
TR
}
class HelloInput {
private final String name;
private final Language language;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public HelloInput(
@JsonProperty("name") String name, @JsonProperty("language") Language language) {
this.name = name;
this.language = language;
}
@JsonProperty("name")
public String getName() {
return name;
}
@JsonProperty("language")
public Language getLanguage() {
return language;
}
}
class HelloOutput {
private final String message;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public HelloOutput(@JsonProperty("message") String message) {
this.message = message;
}
@JsonProperty("message")
public String getMessage() {
return message;
}
}
class EchoInput {
private final String message;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public EchoInput(@JsonProperty("message") String message) {
this.message = message;
}
@JsonProperty("message")
public String getMessage() {
return message;
}
}
class EchoOutput {
private final String message;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public EchoOutput(@JsonProperty("message") String message) {
this.message = message;
}
@JsonProperty("message")
public String getMessage() {
return message;
}
}
@Operation
HelloOutput hello(HelloInput input);
@Operation
EchoOutput echo(EchoInput input);
}
Develop a Nexus Service and Operation handlers
Nexus Operation handlers are typically defined in the same Worker as the underlying Temporal primitives they abstract. Operation handlers can decide if a given Nexus Operation will be synchronous or asynchronous. They can execute arbitrary code, and invoke underlying Temporal primitives such as a Workflow, Query, Signal, or Update.
The io.temporal.nexus.*
packages have utilities to help create Nexus Operations:
Nexus.getOperationContext().getWorkflowClient()
- Get the Temporal Client that the Worker was initialized with for synchronous handlers backed by Temporal primitives such as Signals and QueriesWorkflowRunOperation.fromWorkflowMethod
- Run a Workflow as an asynchronous Nexus Operation
This example starts with a sync Operation handler example using the OperationHandler.sync
method, and then shows how to create an async Operation handler that uses WorkflowRunOperation.fromWorkflowMethod
to start a handler Workflow from a Nexus Operation.
Develop a Synchronous Nexus Operation handler
The OperationHandler.sync
method is for exposing simple RPC handlers.
Its handler function can access an SDK client that can be used for signaling, querying, and listing Workflows.
However, implementations are free to make arbitrary calls to other services or databases, or perform computations such as this one:
core/src/main/java/io/temporal/samples/nexus/handler/NexusServiceImpl.java
// To create a service implementation, annotate the class with @ServiceImpl and provide the
// interface that the service implements. The service implementation class should have methods that
// return OperationHandler that correspond to the operations defined in the service interface.
@ServiceImpl(service = NexusService.class)
public class NexusServiceImpl {
@OperationImpl
public OperationHandler<NexusService.EchoInput, NexusService.EchoOutput> echo() {
// OperationHandler.sync is intended for exposing simple RPC handlers.
return OperationHandler.sync(
// The method is for making arbitrary short calls to other services or databases, or
// performing simple computations such as this one. Users can also access a workflow client by
// calling
// Nexus.getOperationContext().getWorkflowClient(ctx) to make arbitrary Temporal client
// calls such as signaling, querying, or listing workflows.
(ctx, details, input) -> new NexusService.EchoOutput(input.getMessage()));
}
// ...
}
Develop an Asynchronous Nexus Operation handler to start a Workflow
Use the WorkflowRunOperation.fromWorkflowMethod
method, which is the easiest way to expose a Workflow as an operation.
core/src/main/java/io/temporal/samples/nexus/handler/NexusServiceImpl.java
// To create a service implementation, annotate the class with @ServiceImpl and provide the
// interface that the service implements. The service implementation class should have methods that
// return OperationHandler that correspond to the operations defined in the service interface.
@ServiceImpl(service = NexusService.class)
public class NexusServiceImpl {
// ...
@OperationImpl
public OperationHandler<NexusService.HelloInput, NexusService.HelloOutput> hello() {
// Use the WorkflowRunOperation.fromWorkflowMethod constructor, which is the easiest
// way to expose a workflow as an operation.
return WorkflowRunOperation.fromWorkflowMethod(
(ctx, details, input) ->
Nexus.getOperationContext()
.getWorkflowClient()
.newWorkflowStub(
HelloHandlerWorkflow.class,
// Workflow IDs should typically be business-meaningful IDs and are used to
// dedupe workflow starts.
// For this example, we're using the request ID allocated by Temporal when
// the
// caller workflow schedules
// the operation. This ID is guaranteed to be stable across retries of this
// operation.
//
// Task queue defaults to the task queue this operation is handled on.
WorkflowOptions.newBuilder().setWorkflowId(details.getRequestId()).build())
::hello);
}
}
Workflow IDs should typically be business-meaningful IDs and are used to dedupe Workflow starts. In general, the ID should be passed in the Operation input as part of the Nexus Service contract.
Map a Nexus Operation input to multiple Workflow arguments
A Nexus Operation can only take one input parameter. If you want a Nexus Operation to start a Workflow that takes multiple arguments use the WorkflowRunOperation.fromWorkflowHandle
method.
@ServiceImpl(service = NexusService.class)
public class NexusServiceImpl {
// ...
@OperationImpl
public OperationHandler<NexusService.HelloInput, NexusService.HelloOutput> hello() {
// If the operation input parameters are different from the workflow input parameters,
// use the WorkflowRunOperation.fromWorkflowHandler constructor and the appropriate constructor
// method on WorkflowHandle to map the Nexus input to the workflow parameters.
return WorkflowRunOperation.fromWorkflowHandle(
(ctx, details, input) ->
WorkflowHandle.fromWorkflowMethod(
Nexus.getOperationContext()
.getWorkflowClient()
.newWorkflowStub(
HelloHandlerWorkflow.class,
// Workflow IDs should typically be business meaningful IDs and are used
// to
// dedupe workflow starts.
// For this example, we're using the request ID allocated by Temporal
// when
// the
// caller workflow schedules
// the operation, this ID is guaranteed to be stable across retries of
// this
// operation.
//
// Task queue defaults to the task queue this operation is handled on.
WorkflowOptions.newBuilder()
.setWorkflowId(details.getRequestId())
.build())
::hello,
input.getName(),
input.getLanguage()));
}
}
Register a Nexus Service in a Worker
After developing an asynchronous Nexus Operation handler to start a Workflow, the next step is to register a Nexus Service in a Worker.
core/src/main/java/io/temporal/samples/nexus/handler/HandlerWorker.java
package io.temporal.samples.nexus.handler;
import io.temporal.client.WorkflowClient;
import io.temporal.samples.nexus.options.ClientOptions;
import io.temporal.worker.Worker;
import io.temporal.worker.WorkerFactory;
public class HandlerWorker {
public static final String DEFAULT_TASK_QUEUE_NAME = "my-handler-task-queue";
public static void main(String[] args) {
WorkflowClient client = ClientOptions.getWorkflowClient(args);
WorkerFactory factory = WorkerFactory.newInstance(client);
Worker worker = factory.newWorker(DEFAULT_TASK_QUEUE_NAME);
worker.registerWorkflowImplementationTypes(HelloHandlerWorkflowImpl.class);
worker.registerNexusServiceImplementation(new NexusServiceImpl());
factory.start();
}
}
Develop a caller Workflow that uses the Nexus Service
Import the Service API package that has the necessary service and operation names and input/output types to execute a Nexus Operation from the caller Workflow:
core/src/main/java/io/temporal/samples/nexus/caller/EchoCallerWorkflowImpl.java
package io.temporal.samples.nexus.caller;
import io.temporal.samples.nexus.service.NexusService;
import io.temporal.workflow.NexusOperationOptions;
import io.temporal.workflow.NexusServiceOptions;
import io.temporal.workflow.Workflow;
import java.time.Duration;
public class EchoCallerWorkflowImpl implements EchoCallerWorkflow {
NexusService nexusService =
Workflow.newNexusServiceStub(
NexusService.class,
NexusServiceOptions.newBuilder()
.setOperationOptions(
NexusOperationOptions.newBuilder()
.setScheduleToCloseTimeout(Duration.ofSeconds(10))
.build())
.build());
@Override
public String echo(String message) {
return nexusService.echo(new NexusService.EchoInput(message)).getMessage();
}
}
core/src/main/java/io/temporal/samples/nexus/caller/HelloCallerWorkflowImpl.java
package io.temporal.samples.nexus.caller;
import io.temporal.samples.nexus.service.NexusService;
import io.temporal.workflow.NexusOperationHandle;
import io.temporal.workflow.NexusOperationOptions;
import io.temporal.workflow.NexusServiceOptions;
import io.temporal.workflow.Workflow;
import java.time.Duration;
public class HelloCallerWorkflowImpl implements HelloCallerWorkflow {
NexusService nexusService =
Workflow.newNexusServiceStub(
NexusService.class,
NexusServiceOptions.newBuilder()
.setOperationOptions(
NexusOperationOptions.newBuilder()
.setScheduleToCloseTimeout(Duration.ofSeconds(10))
.build())
.build());
@Override
public String hello(String message, NexusService.Language language) {
NexusOperationHandle<NexusService.HelloOutput> handle =
Workflow.startNexusOperation(
nexusService::hello, new NexusService.HelloInput(message, language));
// Optionally wait for the operation to be started. NexusOperationExecution will contain the
// operation token in case this operation is asynchronous.
handle.getExecution().get();
return handle.getResult().get().getMessage();
}
}
Register the caller Workflow in a Worker
After developing the caller Workflow, the next step is to register it with a Worker.
core/src/main/java/io/temporal/samples/nexus/caller/CallerWorker.java
package io.temporal.samples.nexus.caller;
import io.temporal.client.WorkflowClient;
import io.temporal.samples.nexus.options.ClientOptions;
import io.temporal.worker.Worker;
import io.temporal.worker.WorkerFactory;
import io.temporal.worker.WorkflowImplementationOptions;
import io.temporal.workflow.NexusServiceOptions;
import java.util.Collections;
public class CallerWorker {
public static final String DEFAULT_TASK_QUEUE_NAME = "my-caller-workflow-task-queue";
public static void main(String[] args) {
WorkflowClient client = ClientOptions.getWorkflowClient(args);
WorkerFactory factory = WorkerFactory.newInstance(client);
Worker worker = factory.newWorker(DEFAULT_TASK_QUEUE_NAME);
worker.registerWorkflowImplementationTypes(
WorkflowImplementationOptions.newBuilder()
.setNexusServiceOptions(
Collections.singletonMap(
"NexusService",
NexusServiceOptions.newBuilder().setEndpoint("my-nexus-endpoint-name").build()))
.build(),
EchoCallerWorkflowImpl.class,
HelloCallerWorkflowImpl.class);
factory.start();
}
}
Develop a starter to start the caller Workflow
To initiate the caller Workflow, a starter program is used.
core/src/main/java/io/temporal/samples/nexus/caller/CallerStarter.java
package io.temporal.samples.nexus.caller;
import io.temporal.api.common.v1.WorkflowExecution;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowOptions;
import io.temporal.samples.nexus.options.ClientOptions;
import io.temporal.samples.nexus.service.NexusService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CallerStarter {
private static final Logger logger = LoggerFactory.getLogger(CallerStarter.class);
public static void main(String[] args) {
WorkflowClient client = ClientOptions.getWorkflowClient(args);
WorkflowOptions workflowOptions =
WorkflowOptions.newBuilder().setTaskQueue(CallerWorker.DEFAULT_TASK_QUEUE_NAME).build();
EchoCallerWorkflow echoWorkflow =
client.newWorkflowStub(EchoCallerWorkflow.class, workflowOptions);
WorkflowExecution execution = WorkflowClient.start(echoWorkflow::echo, "Nexus Echo 👋");
logger.info(
"Started EchoCallerWorkflow workflowId: {} runId: {}",
execution.getWorkflowId(),
execution.getRunId());
logger.info("Workflow result: {}", echoWorkflow.echo("Nexus Echo 👋"));
HelloCallerWorkflow helloWorkflow =
client.newWorkflowStub(HelloCallerWorkflow.class, workflowOptions);
execution = WorkflowClient.start(helloWorkflow::hello, "Nexus", NexusService.Language.EN);
logger.info(
"Started HelloCallerWorkflow workflowId: {} runId: {}",
execution.getWorkflowId(),
execution.getRunId());
logger.info("Workflow result: {}", helloWorkflow.hello("Nexus", NexusService.Language.ES));
}
}
Make Nexus calls across Namespaces with a development Server
Follow the steps below to run the Nexus handler Worker, the Nexus caller Worker, and the starter app.
Run Workers connected to a local development server
Run the Nexus handler Worker:
./gradlew -q execute -PmainClass=io.temporal.samples.nexus.handler.HandlerWorker \
--args="-target-host localhost:7233 -namespace my-target-namespace"
In another terminal window, run the Nexus caller Worker:
./gradlew -q execute -PmainClass=io.temporal.samples.nexus.caller.CallerWorker \
--args="-target-host localhost:7233 -namespace my-caller-namespace"
Start a caller Workflow
With the Workers running, the final step in the local development process is to start a caller Workflow.
Run the starter:
./gradlew -q execute -PmainClass=io.temporal.samples.nexus.caller.CallerStarter \
--args="-target-host localhost:7233 -namespace my-caller-namespace"
This will result in:
[main] INFO i.t.s.nexus.caller.CallerStarter - Started workflow workflowId: 9b3de8ba-28ae-42fb-8087-bdedf4cecd39 runId: 404a2529-764d-4d1d-9de5-8a9475e40fba
[main] INFO i.t.s.nexus.caller.CallerStarter - Workflow result: Nexus Echo 👋
[main] INFO i.t.s.nexus.caller.CallerStarter - Started workflow workflowId: 9cb29897-356a-4714-87b7-aa2f00784a46 runId: 7e71e62a-db50-49da-b081-24b61016a0fc
[main] INFO i.t.s.nexus.caller.CallerStarter - Workflow result: ¡Hola! Nexus 👋
Canceling a Nexus Operation
To cancel a Nexus Operation from within a Workflow, create a CancellationScope
using the Workflow.newCancellationScope
API. Workflow.newCancellationScope
takes a Runnable
. Any SDK methods started in this runnable, such as Nexus operations, will be associated with this scope. Workflow.newCancellationScope
returns a new scope that, when the cancel()
method is called, cancels the context and any SDK method that was started in the scope.
The promise returned by Workflow.startNexusOperation
is resolved when the operation finishes, whether it succeeds, fails, times out, or is canceled.
Only asynchronous operations can be canceled in Nexus, as cancelation is sent using an operation token. The Workflow or other resources backing the operation may choose to ignore the cancelation request. If ignored, the operation may enter a terminal state.
Once the caller Workflow completes, the caller's Nexus Machinery will not make any further attempts to cancel operations that are still running. It's okay to leave operations running in some use cases. To ensure cancelations are delivered, wait for all pending operations to finish before exiting the Workflow.
See the Nexus cancelation sample for reference.
Make Nexus calls across Namespaces in Temporal Cloud
This section assumes you are already familiar with how connect a Worker to Temporal Cloud.
The same source code is used in this section, but the tcld
CLI will be used to create Namespaces and the Nexus Endpoint, and mTLS client certificates will be used to securely connect the caller and handler Workers to their respective Temporal Cloud Namespaces.
Install the latest tcld
CLI and generate certificates
To install the latest version of the tcld
CLI, run the following command (on MacOS):
brew install temporalio/brew/tcld
If you don't already have certificates, you can generate them for mTLS Worker authentication using the command below:
tcld gen ca --org $YOUR_ORG_NAME --validity-period 1y --ca-cert ca.pem --ca-key ca.key
These certificates will be valid for one year.
Create caller and handler Namespaces
Before deploying to Temporal Cloud, ensure that the appropriate Namespaces are created for both the caller and handler. If you already have these Namespaces, you don't need to do this.
tcld login
tcld namespace create \
--namespace <your-caller-namespace> \
--region us-west-2 \
--ca-certificate-file 'path/to/your/ca.pem' \
--retention-days 1
tcld namespace create \
--namespace <your-target-namespace> \
--region us-west-2 \
--ca-certificate-file 'path/to/your/ca.pem' \
--retention-days 1
Alternatively, you can create Namespaces through the UI: https://cloud.temporal.io/Namespaces.
Create a Nexus Endpoint to route requests from caller to handler
To create a Nexus Endpoint you must have a Developer account role or higher, and have NamespaceAdmin permission on the --target-namespace
.
tcld nexus endpoint create \
--name <my-nexus-endpoint-name> \
--target-task-queue my-handler-task-queue \
--target-namespace <my-target-namespace.account> \
--allow-namespace <my-caller-namespace.account> \
--description-file ./core/src/main/java/io/temporal/samples/nexus/service/description.md
The --allow-namespace
is used to build an Endpoint allowlist of caller Namespaces that can use the Nexus Endpoint, as described in Runtime Access Control.
Alternatively, you can create a Nexus Endpoint through the UI: https://cloud.temporal.io/nexus.
Run Workers Connected to Temporal Cloud
Run the handler Worker:
./gradlew -q execute -PmainClass=io.temporal.samples.nexus.handler.HandlerWorker \
--args="-target-host <your-target-namespace.account>.tmprl.cloud:7233 \
-namespace <your-target-namespace.account> \
-client-cert 'path/to/your/ca.pem' \
-client-key 'path/to/your/ca.key'"
Run the caller Worker:
./gradlew -q execute -PmainClass=io.temporal.samples.nexus.caller.CallerWorker \
--args="-target-host <your-caller-namespace.account>.tmprl.cloud:7233 \
-namespace <your-caller-namespace.account> \
-client-cert 'path/to/your/ca.pem' \
-client-key 'path/to/your/ca.key'"
Start a caller Workflow
./gradlew -q execute -PmainClass=io.temporal.samples.nexus.caller.CallerStarter \
--args="-target-host <your-caller-namespace.account>.tmprl.cloud:7233 \
-namespace <your-caller-namespace.account> \
-client-cert 'path/to/your/ca.pem' \
-client-key 'path/to/your/ca.key'"
This will result in:
[main] INFO i.t.s.nexus.caller.CallerStarter - Started workflow workflowId: 9b3de8ba-28ae-42fb-8087-bdedf4cecd39 runId: 404a2529-764d-4d1d-9de5-8a9475e40fba
[main] INFO i.t.s.nexus.caller.CallerStarter - Workflow result: Nexus Echo 👋
[main] INFO i.t.s.nexus.caller.CallerStarter - Started workflow workflowId: 9cb29897-356a-4714-87b7-aa2f00784a46 runId: 7e71e62a-db50-49da-b081-24b61016a0fc
[main] INFO i.t.s.nexus.caller.CallerStarter - Workflow result: ¡Hola! Nexus 👋
Observability
Web UI
A synchronous Nexus Operation will surface in the caller Workflow as follows, with just NexusOperationScheduled
and NexusOperationCompleted
events in the caller's Workflow history:

Observability Sync
An asynchronous Nexus Operation will surface in the caller Workflow as follows, with NexusOperationScheduled
, NexusOperationStarted
, and NexusOperationCompleted
, in the caller's Workflow history:

Observability Async
Temporal CLI
Use the workflow describe
command to show pending Nexus Operations in the caller Workflow and any attached callbacks on the handler Workflow:
temporal workflow describe -w <ID>
Nexus events are included in the caller's Workflow history:
temporal workflow show -w <ID>
For asynchronous Nexus Operations the following are reported in the caller's history:
NexusOperationScheduled
NexusOperationStarted
NexusOperationCompleted
For synchronous Nexus Operations the following are reported in the caller's history:
NexusOperationScheduled
NexusOperationCompleted
NexusOperationStarted
isn't reported in the caller's history for synchronous operations.
Learn more
- Read the high-level description of the Temporal Nexus feature and watch the Nexus keynote and demo.
- Learn how Nexus works in the Nexus deep dive talk and Encyclopedia.
- Deploy Nexus Endpoints in production with Temporal Cloud.