Skip to content

Sample code for Nexus Standalone#782

Open
Evanthx wants to merge 2 commits into
mainfrom
sano
Open

Sample code for Nexus Standalone#782
Evanthx wants to merge 2 commits into
mainfrom
sano

Conversation

@Evanthx

@Evanthx Evanthx commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Sample code for Standalone Nexus operations. This cannot be checked in before the matching SDK changes - temporalio/sdk-java#2872

@Quinn-With-Two-Ns

Copy link
Copy Markdown
Contributor

Make sure to link this in the README https://github.com/temporalio/samples-java/blob/main/README.md

5. **Client options and interceptors** — set the identity and data converter, and register two
logging interceptors.

> [!WARNING]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: I would put this at the top


### Running

Start a Temporal server with the standalone-Nexus dynamic configs enabled:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should call out the version of the dev server users need

package io.temporal.samples.nexusstandalone.service;

// A helper method to generate workflow IDs.
public final class GreetingIds {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need this helper, just inline this

* environment variables) to point at a different server or namespace — for example a Temporal Cloud
* namespace with an API key.
*/
public class ClientOptions {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd just inline this like all the other samples

public static WorkflowClient getWorkflowClient() {
ClientConfigProfile profile;
try {
String configFilePath =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have special client config loading code? Other samples just do

   ClientConfigProfile profile;
    try {
      profile = ClientConfigProfile.load();
    } catch (IOException e) {
      throw new RuntimeException("Failed to load client configuration", e);
    }

NexusServiceClient<GreetingNexusService> greetingClient =
nexusClient.newNexusServiceClient(GreetingNexusService.class, ENDPOINT_NAME);

demonstrateExecute(greetingClient);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please inline all these functions, they only have one caller and make the code harder to follow


// executeAsync(...) is the same but returns a CompletableFuture instead of blocking.
CompletableFuture<GreetingOutput> future =
nexusClient.executeAsync(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would show execute here to keep it simpler

nexusClient.newNexusServiceClient(GreetingNexusService.class, ENDPOINT_NAME);

demonstrateExecute(greetingClient);
demonstrateCancel(greetingClient);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we demonstrated cancel or terminate in other SDK language samples. Not strongly opposed, but it is a lot? Whatever we decided to do we should be consistent

NexusServiceClient<GreetingNexusService> greetingClient =
nexusClient.newNexusServiceClient(GreetingNexusService.class, ENDPOINT_NAME);

demonstrateExecute(greetingClient);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing we discussed with Product we want to show is getting a handle to an operation that has already been started, would be good to show that when we show execute


demonstrateExecute(greetingClient);
demonstrateCancel(greetingClient);
demonstrateTerminate(greetingClient, client);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also demonstrate start

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using the workflow operation specifically. I would show starting and then getting the result in two steps. That is by far the most common use of Nexus so we should show that IMO

demonstrateCancel(greetingClient);
demonstrateTerminate(greetingClient, client);
demonstrateVisibility(nexusClient);
demonstrateClientOptionsAndInterceptors(stubs, namespace);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interceptors are a very advanced option I would leave them off this sample

logger.info(
"Operation id={} final status: {}",
handle.getNexusOperationId(),
awaitTerminalStatus(handle, Duration.ofSeconds(10)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be encouraging users to manually poll describe, users can just get the result of the operation through the handle

private static final Logger logger = LoggerFactory.getLogger(StandaloneClientStarter.class);

// Must match the Nexus endpoint configured on the server (see README).
public static final String ENDPOINT_NAME = "nexusstandalone-endpoint";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
public static final String ENDPOINT_NAME = "nexusstandalone-endpoint";
public static final String ENDPOINT_NAME = "nexus-standalone-operation-endpoint";

// A per-run suffix appended to workflow-backed operation names so their backing workflow IDs are
// unique on each run. Without this, re-running against the same server (no restart) would reuse
// deterministic workflow IDs from the previous run and collide.
private static final String RUN_ID = UUID.randomUUID().toString().substring(0, 8);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Run ID already has a meaning in Temporal as a server generated UUID I wouldn't overload the term here. No strong opinion on the term other then not using an existing term

Create the namespace and the Nexus endpoint:

```bash
temporal operator namespace create --namespace default

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the dev server

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants