# Add a Standalone Activity

> For the complete documentation index, see [llms.txt](https://docs.temporal.io/llms.txt).
> Any documentation page is available as raw Markdown by appending `.md` to its URL.
> For related questions, query https://docs.temporal.io/api/retrieve?q=<question>&jtb=<job-to-be-done>&src=md-page&agent=<your-name> (no auth; returns JSON `{content, source_url, page_url}`, with `source_url` pointing to Markdown for docs pages). Don't include personal data or credentials.

> An Activity-backed Operation sends one notification with retries and no Workflow, keyed on the Nexus request Id so a server retry cannot send it twice.

> **📝 Note:**
> Keep these running
>
> This step uses the processes started earlier. Leave them running:
>
> - The development server, from [step 4](/develop/java/nexus/development-walkthrough/implement-the-service#start-the-development-server). It must have been started with `activity.enableCallbacks=true`, or `notifyRequester` fails with `completion callbacks are not enabled for this namespace`.
> - The handler Worker, from [step 4](/develop/java/nexus/development-walkthrough/implement-the-service#run-the-worker).
> - The caller Worker, from [step 6](/develop/java/nexus/development-walkthrough/call-the-service#call-the-operations-from-a-caller-workflow).
>

The last Operation, `notifyRequester`, tells the requester whether their approval was `APPROVED` or `DENIED`. It is a single outbound notification with no state and nothing to wait for, so it is backed by a [Standalone Activity](/nexus/standalone-activity), as chosen in [step 3](/develop/java/nexus/development-walkthrough/choose-backing-implementation).

## Write the Activity

The Activity is ordinary and has nothing Nexus-specific in it; a Workflow could call the same Activity unchanged. In this walkthrough it is a placeholder that sends no email. Real logic would call an email provider, push to a notification service, or write to an outbox.

<!--SNIPSTART samples-java-nexus-walkthrough-activities-->

[core/src/main/java/io/temporal/samples/nexuswalkthrough/handler/ApprovalActivities.java](https://github.com/temporalio/samples-java/blob/main/core/src/main/java/io/temporal/samples/nexuswalkthrough/handler/ApprovalActivities.java)

```java
public interface ApprovalActivities {

  /** STEP 4 placeholder - real logic would apply policy, check limits, or call a risk service. */
  @ActivityMethod
  void evaluateAutoDecision(String itemId, double amount);

  /** STEP 4 placeholder - real logic would page an approver or open a ticket. */
  @ActivityMethod
  void notifyApproverOfPendingRequest(String itemId, String requester);

  /**
   * STEP 9 - The Standalone Activity behind the notifyRequester Operation. One outbound
   * notification, no state, nothing to wait for. In this sample it only logs; real logic would call
   * an email provider, push to a notification service, or write to an outbox.
   */
  @ActivityMethod
  NotifyRequesterOutput notifyRequester(String requester, NotifyRequesterInput.Decision decision);
}
```

<!--SNIPEND-->

## Back the Operation with it

Use `TemporalOperationHandler`, but start an Activity on the [Client](/nexus/temporal-operation-handler#the-nexus-aware-client) instead of a Workflow. The Operation starts an Activity Execution with no parent Workflow and completes when the Activity returns.

The Activity provides retries, timeouts, and a record of every attempt. The Operation provides the contract, so other teams can trigger the notification without sharing your code or your Namespace.

<!--SNIPSTART samples-java-nexus-walkthrough-notify-requester-->

[core/src/main/java/io/temporal/samples/nexuswalkthrough/handler/ApprovalServiceImpl.java](https://github.com/temporalio/samples-java/blob/main/core/src/main/java/io/temporal/samples/nexuswalkthrough/handler/ApprovalServiceImpl.java)

```java
  @OperationImpl
  public OperationHandler<NotifyRequesterInput, NotifyRequesterOutput> notifyRequester() {
    return TemporalOperationHandler.create(
        (ctx, client, input) ->
            client.startActivity(
                ApprovalActivities.class,
                ApprovalActivities::notifyRequester,
                input.getRequester(),
                input.getDecision(),
                StartActivityOptions.newBuilder()
                    // Deriving the Activity Id from the request Id keeps a retried Nexus start
                    // request targeting the same Activity Execution instead of sending a second
                    // notification.
                    .setId("notify-" + ctx.getRequestId())
                    .setTaskQueue(HandlerWorker.DEFAULT_TASK_QUEUE_NAME)
                    .setStartToCloseTimeout(Duration.ofSeconds(10))
                    .setScheduleToCloseTimeout(Duration.ofMinutes(5))
                    .setRetryOptions(RetryOptions.newBuilder().setMaximumAttempts(3).build())
                    .build()));
  }
```

<!--SNIPEND-->

### Options an Activity-backed Operation requires

`StartActivityOptions` needs an **Activity Id**, unique within the Namespace, because there is no parent Workflow to scope it.

The Task Queue is optional and defaults to the one the Operation runs on. Set it to run notifications on their own Worker fleet.

**Derive the Activity Id from the Nexus request Id so that server retries don't send a second email.** Retried Nexus start requests carry the same request Id, so every retry lands on the same Activity Id and the notification goes out once. Use the same pattern for any externally visible side effect that can't be undone, such as charging a card, posting to a webhook, or creating a ticket.

To make several Operations share one Activity Execution and all receive its result, derive the Id from the Operation *input* instead. See [Nexus Standalone Activity](/nexus/standalone-activity#required-options).

## Register the Activity on the Worker

Register the Activity implementation on the Worker that hosts the Nexus Service. An Activity-backed Operation needs no Workflow registration.

<!--SNIPSTART samples-java-nexus-walkthrough-handler-worker-->

[core/src/main/java/io/temporal/samples/nexuswalkthrough/handler/HandlerWorker.java](https://github.com/temporalio/samples-java/blob/main/core/src/main/java/io/temporal/samples/nexuswalkthrough/handler/HandlerWorker.java)

```java
  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(ApprovalWorkflowImpl.class);
    worker.registerActivitiesImplementations(new ApprovalActivitiesImpl());
    worker.registerNexusServiceImplementation(new ApprovalServiceImpl());

    factory.start();
  }
```

<!--SNIPEND-->

## Cancellation needs heartbeating

This notification finishes immediately, so cancellation doesn't matter here. For a longer Activity-backed Operation it does: a cancellation request doesn't interrupt an Activity, so an Activity that never heartbeats runs until it completes or times out. Before shipping a long-running Activity-backed Operation, read [Activity cancellation](/activity-execution#cancellation).

## Run it

From the command line, this Operation looks like every other one:

```bash title="Run 1 of 2: notify the requester"
temporal nexus operation execute \
  --namespace approval-caller-namespace \
  --endpoint approval-endpoint \
  --service temporal.samples.approval.v1.ApprovalService \
  --operation NotifyRequester \
  --operation-id notify-1 \
  --input '{"requester":"tao@example.com","decision":"APPROVED"}'
```

```
Results:
  Status  COMPLETED
  Result  {"deliveredTo":"tao@example.com"}
```

On the handler side there is no Workflow, only an Activity Execution with no parent:

```bash title="Run 2 of 2: list the Standalone Activity"
temporal activity list --namespace approval-handler-namespace
```

```
  Status      ActivityId                                   Type             StartTime
  Completed   notify-0f816331-6b85-404d-9dfa-dd249041c1ea  NotifyRequester  now
```

The handler derived the Activity Id from the Nexus request Id. Running the command again with a new
`--operation-id` creates a second Activity Execution, but a server retry of the *same* request reuses
the first.

If this fails with `completion callbacks are not enabled for this namespace`, the development
server was started without `activity.enableCallbacks=true`. See
[step 4](/develop/java/nexus/development-walkthrough/implement-the-service#start-the-development-server).

> **💡 Tip:**
> RESOURCES
>
> - [Nexus Standalone Activity](/nexus/standalone-activity) for the full concept and options.
> - [Standalone Activity](/standalone-activity) for Activity Executions outside a Workflow.
> - [Java: Standalone Activities](/develop/java/activities/standalone-activities) for the SDK API.
>

## Next step

- [Step 10: Finish the flow](/develop/java/nexus/development-walkthrough/call-the-standalone-activity): The whole approval end to end.
