Skip to main content

Standalone Nexus Operations

View Markdown

Standalone Nexus Operations let you run Nexus Operation Executions independently, without being orchestrated by a Workflow. Instead of calling a Nexus Operation from within a Workflow Definition using Workflow.newNexusServiceStub(), you execute a Standalone Nexus Operation directly from a Nexus service client created from a NexusClient using NexusClient.newNexusServiceClient().

Standalone Nexus Operations use the same Nexus Service contract, Operation handlers, and Worker setup as Workflow-driven Operations — only the execution path differs. See the Nexus feature guide for details on defining a Service contract, developing Operation handlers, and registering a Service in a Worker.

This page focuses on the client-side APIs that are unique to Standalone Nexus Operations:

info

The code on this page is in the java-sandbox/nexus/standalone-operations project, derived from the Java Nexus Standalone sample. Run the commands on this page from that project's folder.

Prerequisites​

Standalone Nexus Operations are a Public Preview feature and require Temporal CLI v1.9.0 or higher.

1. Install and verify the Temporal CLI​

The temporal nexus operation commands require Temporal CLI v1.9.0 or higher. See Temporal CLI support for install options, then verify:

temporal --version

Which should output v1.9.0 or higher.

2. Start a local dev server​

Start the dev server with Standalone Nexus Operations enabled and the caller and handler Namespaces pre-created:

temporal server start-dev \
--namespace my-caller-namespace \
--namespace my-handler-namespace \
--dynamic-config-value nexusoperation.enableStandalone=true

Without nexusoperation.enableStandalone=true, Temporal CLI v1.9.x dev servers reject Standalone Nexus Operations with Standalone Nexus operation is disabled.

The starter and Worker connect to two different Namespaces (a caller Namespace and a handler Namespace), mirroring how Nexus crosses Namespace boundaries.

To run the examples on this page, create a Nexus Endpoint that routes to the handler Namespace and the Worker's Task Queue:

temporal operator nexus endpoint create \
--name my-nexus-endpoint \
--target-namespace my-handler-namespace \
--target-task-queue nexus-handler-queue

Start the handler Worker in the handler Namespace:

TEMPORAL_NAMESPACE=my-handler-namespace \
mvn compile exec:java -Dexec.mainClass=io.temporal.docs.nexus.standaloneoperations.handler.HandlerWorker

Run the starter in the caller Namespace (from a separate terminal):

TEMPORAL_NAMESPACE=my-caller-namespace \
mvn compile exec:java -Dexec.mainClass=io.temporal.docs.nexus.standaloneoperations.StandaloneClientStarter

The starter runs each example on this page in turn and logs output like the following. Operation IDs are fixed, so the list and count results grow across runs.

execute() returned: Hello, execute!
executeAsync() returned: Hello, executeAsync!
getHandle(id=execute-nexus) then getResult() returned: Hello, execute!
start(id=start-nexus) then getResult() returned: Hello, start!
Started 'to-cancel' id=start-and-cancel-nexus, requesting cancellation
Operation id=start-and-cancel-nexus ended as expected after cancel: Nexus operation failed: operationId='start-and-cancel-nexus'
OperationId: start-and-cancel-nexus, Operation: startGreeting, Status: NEXUS_OPERATION_EXECUTION_STATUS_CANCELED
OperationId: start-nexus, Operation: greet, Status: NEXUS_OPERATION_EXECUTION_STATUS_COMPLETED
OperationId: execute-async-nexus, Operation: greet, Status: NEXUS_OPERATION_EXECUTION_STATUS_COMPLETED
OperationId: execute-nexus, Operation: greet, Status: NEXUS_OPERATION_EXECUTION_STATUS_COMPLETED
Total Nexus operations: 4
group values=[[Canceled]] count=1
group values=[[Completed]] count=3

Execute a Standalone Nexus Operation​

To execute a Standalone Nexus Operation, first create a NexusClient, then derive a typed NexusServiceClient from it with newNexusServiceClient(), bound to a specific Nexus Endpoint and Service. The endpoint must be pre-created on the server. Then call start() or execute() from application code (for example, a starter program), not from inside a Workflow Definition.

java-sandbox/nexus/standalone-operations/src/main/java/io/temporal/docs/nexus/standaloneoperations/StandaloneClientStarter.java

// A single NexusClient is the entry point: it serves Visibility queries (list/count) and
// produces service-bound clients.
NexusClient nexusClient =
NexusClient.newInstance(
stubs, NexusClientOptions.newBuilder().setNamespace(namespace).build());
// Typed service client: dispatches operations by method reference on the service interface.
NexusServiceClient<GreetingNexusService> greetingClient =
nexusClient.newNexusServiceClient(GreetingNexusService.class, ENDPOINT_NAME);

execute() waits for the Operation to complete and returns the result. Both methods take a StartNexusOperationOptions whose id is required — the SDK never generates one for you. scheduleToCloseTimeout is optional and defaults to the maximum allowed by the Temporal server.

java-sandbox/nexus/standalone-operations/src/main/java/io/temporal/docs/nexus/standaloneoperations/StandaloneClientStarter.java

// execute(...) starts the operation and blocks until it completes, returning the result in one
// call. Used here on the synchronous 'greet' operation.
String operationId = "execute-nexus";
GreetingOutput executed =
greetingClient.execute(
GreetingNexusService::greet,
StartNexusOperationOptions.newBuilder()
.setId(operationId)
.setScheduleToCloseTimeout(Duration.ofSeconds(10))
.build(),
new GreetingInput("execute"));
logger.info("execute() returned: {}", executed.getMessage());

executeAsync() is the same but returns a CompletableFuture instead of blocking.

java-sandbox/nexus/standalone-operations/src/main/java/io/temporal/docs/nexus/standaloneoperations/StandaloneClientStarter.java

// executeAsync(...) is the same as execute(...), but returns a CompletableFuture instead of
// blocking.
CompletableFuture<GreetingOutput> future =
greetingClient.executeAsync(
GreetingNexusService::greet,
basicOptions("execute-async-nexus"),
new GreetingInput("executeAsync"));
GreetingOutput executedAsync = future.get();
logger.info("executeAsync() returned: {}", executedAsync.getMessage());

See the full starter for a complete example that executes, starts, and cancels Operations, gets their results, and lists and counts Operations.

Or use the Temporal CLI to execute a Standalone Nexus Operation:

temporal nexus operation execute \
--namespace my-caller-namespace \
--endpoint my-nexus-endpoint \
--service GreetingNexusService \
--operation greet \
--operation-id my-greet-op \
--input '{"name":"World"}'

Start a Standalone Nexus Operation and Wait for the Result​

start() returns a NexusOperationHandle. Use NexusOperationHandle.getResult() to wait until the Operation completes and retrieve its result. This works for both synchronous and asynchronous Operations.

java-sandbox/nexus/standalone-operations/src/main/java/io/temporal/docs/nexus/standaloneoperations/StandaloneClientStarter.java

// start(...) returns a NexusOperationHandle as soon as the operation is started.
NexusOperationHandle<GreetingOutput> handle =
greetingClient.start(
GreetingNexusService::greet,
basicOptions("start-nexus"),
new GreetingInput("start"));

// getResult() blocks until the operation completes and returns its result.
GreetingOutput greeting = handle.getResult();
logger.info(
"start(id={}) then getResult() returned: {}",
handle.getNexusOperationId(),
greeting.getMessage());

If the Operation completed successfully, the result is returned. If the Operation failed, the failure is thrown as a NexusOperationException. Use getResultAsync() for a non-blocking CompletableFuture, or getResult(long timeout, TimeUnit unit) to bound the wait.

Or use the Temporal CLI to wait for a result by Operation ID:

temporal nexus operation result --namespace my-caller-namespace --operation-id my-greet-op

List Standalone Nexus Operations​

Use NexusClient.listNexusOperationExecutions() to list Standalone Nexus Operation Executions that match a List Filter query. The result is a Stream of operation metadata entries.

Note that listNexusOperationExecutions() is called on a NexusClient, not on the typed NexusServiceClient.

java-sandbox/nexus/standalone-operations/src/main/java/io/temporal/docs/nexus/standaloneoperations/StandaloneClientStarter.java

// listNexusOperationExecutions accepts a List Filter query. Here it filters by the built-in
// Endpoint attribute. Query syntax is documented at https://docs.temporal.io/list-filter .
String query = "Endpoint = \"" + endpointName + "\"";
List<NexusOperationExecutionMetadata> operations =
nexusClient.listNexusOperationExecutions(query).collect(Collectors.toList());
for (NexusOperationExecutionMetadata op : operations) {
logger.info(
"OperationId: {}, Operation: {}, Status: {}",
op.getOperationId(),
op.getOperation(),
op.getStatus());
}

The query parameter accepts List Filter syntax. For example, "Endpoint = 'my-endpoint' AND ExecutionStatus = 'Running'".

Or use the Temporal CLI:

temporal nexus operation list --namespace my-caller-namespace --query 'Endpoint = "my-nexus-endpoint"'

Count Standalone Nexus Operations​

Use NexusClient.countNexusOperationExecutions() to count Standalone Nexus Operation Executions that match a List Filter query.

Note that countNexusOperationExecutions() is called on a NexusClient, not on the typed NexusServiceClient.

java-sandbox/nexus/standalone-operations/src/main/java/io/temporal/docs/nexus/standaloneoperations/StandaloneClientStarter.java

// countNexusOperationExecutions accepts the same List Filter query.
NexusOperationExecutionCount total = nexusClient.countNexusOperationExecutions(query);
logger.info("Total Nexus operations: {}", total.getCount());

// A GROUP BY query returns a count per group value.
NexusOperationExecutionCount grouped =
nexusClient.countNexusOperationExecutions(query + " GROUP BY ExecutionStatus");
for (NexusOperationExecutionCount.AggregationGroup group : grouped.getGroups()) {
logger.info(" group values={} count={}", group.getGroupValues(), group.getCount());
}

A query that ends in a GROUP BY clause (for example, GROUP BY ExecutionStatus) returns a count per group, available through NexusOperationExecutionCount.getGroups().

Or use the Temporal CLI:

temporal nexus operation count --namespace my-caller-namespace --query 'Endpoint = "my-nexus-endpoint"'

Run Standalone Nexus Operations with Temporal Cloud​

The code samples referenced on this page build their client from a ClientConfigProfile loaded from a TOML profile, so the same code works against Temporal Cloud. Point the profile at your Cloud Namespace (or override the connection via TEMPORAL_* environment variables). No code changes are needed.

For full details on connecting to Temporal Cloud, including Namespace creation, Nexus Endpoint setup, certificate generation, and authentication options, see Make Nexus calls across Namespaces in Temporal Cloud and Connect to Temporal Cloud.