# Step 7 - Add messaging

> 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.

> Expose a Signal and an Update on the approval Workflow as Nexus Operations using the Nexus-aware Client.

The approval blocks waiting for a decision. **Messages** give callers a way to interact with it while it waits.

Two Operations get added to the workflow sample problem. Which [message](/sending-messages) type each one uses is decided by what the caller needs back, not by preference.

| Operation | Message type | Why this type |
| --- | --- | --- |
| `remindApprover` | Signal | Fire-and-forget. The caller does not need a response, only for the nudge to happen. |
| `submitDecision` | Update | Changes state *and* returns a result the caller needs — confirmation the decision was recorded. |

That is the whole rule. If the caller can proceed without hearing anything back, a Signal is enough. If the caller needs to know what the message did, it needs an Update.

## Add the handlers to the Workflow

On the Workflow, add a Signal handler that increments the reminder count and an Update handler that records the decision and unblocks the wait.

The Update is what ends the approval. It records `APPROVED` or `DENIED`, which satisfies the condition the Workflow is blocked on, and the Workflow then returns that decision as its result.

<!--SNIPSTART samples-java-nexus-walkthrough-approval-workflow-->

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

```java
public interface ApprovalWorkflow {

  /**
   * The Update handler's name on the wire. The Update-backed Operation has to name the Update
   * explicitly when it starts one, so the name is declared once here and reused there rather than
   * being spelled as a literal in two places.
   */
  String SUBMIT_DECISION_UPDATE = "submitDecision";

  /**
   * STEP 4 - The Workflow method. Its return value is the result of the requestApproval Operation:
   * the Operation completes when this Workflow returns, and the caller receives this value through
   * the Nexus completion callback.
   *
   * <p>Because the Workflow's return value is delivered straight to the caller as the Operation
   * result, it has to be the Operation's declared output type.
   */
  @WorkflowMethod
  RequestApprovalOutput runApproval(RequestApprovalInput input);

  /**
   * STEP 7 - A Signal. Fire-and-forget: the caller gets no result back, which is why a Signal is
   * the right message type for a nudge, and why the contract declares no output for it.
   */
  @SignalMethod
  void remindApprover();

  /**
   * STEP 8 - A Signal that also carries supporting information. Reached through Signal-with-Start,
   * so it may be the message that creates this Workflow.
   */
  @SignalMethod
  void attachContext(String note);

  /**
   * STEP 7 - An Update. The caller needs a result back - confirmation that the decision was
   * recorded - which is what makes this an Update rather than a Signal.
   */
  @UpdateMethod(name = ApprovalWorkflow.SUBMIT_DECISION_UPDATE)
  SubmitDecisionOutput submitDecision(SubmitDecisionInput.Decision decision);

  /**
   * STEP 7 - The Update's validator. An Update can reject a request before it changes anything,
   * which a Signal cannot: a Signal has already been accepted by the time the handler runs.
   *
   * <p>Here it rejects a second decision for an approval that has already been decided. Without it
   * the later decision would silently overwrite the earlier one. A rejected Update does not appear
   * in Event History and does not run the handler.
   */
  @UpdateValidatorMethod(updateName = ApprovalWorkflow.SUBMIT_DECISION_UPDATE)
  void validateSubmitDecision(SubmitDecisionInput.Decision decision);
}
```

<!--SNIPEND-->

## Expose them as Nexus Operations

Both use `TemporalOperationHandler`, and they divide along the line described in [The Nexus-aware Client](/nexus/temporal-operation-handler#the-nexus-aware-client): a Signal is **sync messaging**, and an Update is an **async backing**.

### Signal

Send the Signal through the Client, then return a synchronous result. The Operation completes immediately, during the handler call.

> **⚠️ Caution:**
> The handler has under 10 seconds
>
> A synchronous handler must finish inside the [10-second handler deadline](/cloud/limits#nexus-operation-request-timeout), and the budget you actually get is smaller: the clock starts on the caller's side and the request still has to route through matching.
>
> Sending one Signal is comfortably inside it. A handler that sends several messages, or does slow work before returning, is not. Overrunning gives the caller a context deadline exceeded error, which it then retries with exponential backoff until the schedule-to-close timeout expires.
>

<!--SNIPSTART samples-java-nexus-walkthrough-remind-approver-->

[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<RemindApproverInput, Void> remindApprover() {
    return TemporalOperationHandler.create(
        (ctx, client, input) -> {
          client
              .getWorkflowClient()
              .newWorkflowStub(
                  ApprovalWorkflow.class, ApprovalWorkflowId.forItem(input.getItemId()))
              .remindApprover();
          return TemporalOperationResult.sync(null);
        });
  }
```

<!--SNIPEND-->

### Update

Start the Update on the Client. This is an async backing: the Operation completes when the Update completes, and its result is delivered through the Nexus completion callback. If the Update happens to come back already complete — a retried request, or one that failed validation — the result returns synchronously instead.

An Update-backed Operation carries two requirements. It targets a Workflow that already exists, so a `submitDecision` for a purchase with no approval running fails. And because it is an async backing, there is at most one per Operation invocation, though a handler can still combine it with sync side effects.

### Reject a bad Update before it changes anything

An Update can also refuse a request, which is the other thing a Signal cannot do. By the time a Signal handler runs the message has already been accepted and written to history; there is nowhere left to say no.

The approval uses that. A **validator** runs before the handler and rejects a second decision for an approval that has already been decided — without it, the later decision would silently overwrite the earlier one. A rejected Update never runs the handler, never reaches Event History, and surfaces to the caller as a failed Operation.

The validator is the method annotated `@UpdateValidatorMethod` in the Workflow interface above. It takes the same arguments as the handler, returns nothing, and must not change Workflow state.

<!--SNIPSTART samples-java-nexus-walkthrough-submit-decision-->

[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<SubmitDecisionInput, SubmitDecisionOutput> submitDecision() {
    return TemporalOperationHandler.create(
        (ctx, client, input) ->
            client.startWorkflowUpdate(
                ApprovalWorkflow.class,
                ApprovalWorkflowId.forItem(input.getItemId()),
                ApprovalWorkflow::submitDecision,
                input.getDecision(),
                UpdateOptions.<SubmitDecisionOutput>newBuilder()
                    .setResultClass(SubmitDecisionOutput.class)
                    // The Update to invoke has to be named explicitly; the method reference above
                    // supplies the argument types but not the wire name.
                    .setUpdateName(ApprovalWorkflow.SUBMIT_DECISION_UPDATE)
                    // An Update-backed Operation must wait for the ACCEPTED stage. The Operation
                    // completes later, when the Update completes, through the completion callback.
                    // Any other stage is rejected with "nexus op workflow updates only support
                    // WorkflowUpdateStageAccepted for async updates".
                    .setWaitForStage(WorkflowUpdateStage.ACCEPTED)
                    .build()));
  }
```

<!--SNIPEND-->

## Do not poll for the decision

There is one design mistake worth naming, because it is the most common one in this shape: reaching for a message to fetch the final decision.

The decision is the result of `requestApproval`, and it reaches the caller without anyone asking for it.

When the handler started the approval, Nexus attached a [completion callback](/glossary#nexus-async-completion-callback) to that Workflow. The moment the Workflow returns, the handler's Namespace delivers the callback to the caller's Nexus Machinery, which records a `NexusOperationCompleted` event in the caller Workflow's history. The caller Worker picks that up on its next Workflow Task, and the caller Workflow resumes with the decision. See the [asynchronous Operation lifecycle](/nexus/operations#asynchronous-operation-lifecycle) for the full sequence.

A caller that instead asks the approval for its status in a loop is polling for something already on its way.

Messages are for changing a running approval or nudging it along, not for collecting its outcome. `remindApprover` asks the approver again. `submitDecision` supplies the decision and confirms it landed. Neither is a way to read the result.

Both also stop working the moment the approval completes. The Temporal Service accepts a Signal or an Update only while the Workflow is still running, and rejects one sent to a closed Workflow with `NOT_FOUND: workflow execution already completed`. That happens as soon as the decision lands, not when the [Retention Period](/temporal-service/temporal-server#retention-period) later expires and the Execution is deleted. A second `submitDecision` for an approval that has already been decided fails this way, and so does any attempt to use these Operations to look up a past decision.

If more than one system needs the outcome, see [step 8](/develop/java/nexus/development-walkthrough/send-messages#when-the-approval-already-exists) for how additional callers attach to a running approval and receive the same decision.

## Next

**[Step 8 - Send messages](/develop/java/nexus/development-walkthrough/send-messages)** - call the messaging Operations, and handle an approval that may not exist yet.

Back to the [Microservice Development Walkthrough overview](/develop/java/nexus/development-walkthrough).

> **💡 Tip:**
> RESOURCES
>
> - [Workflow message passing](/encyclopedia/workflow-message-passing) for Signals and Updates.
> - [Handling messages](/handling-messages) for handler constraints.
> - [Temporal Operation Handler](/nexus/temporal-operation-handler) for the sync messaging and async backing distinction.
>
