# Handling & Testing idempotency in java

**URL:** <https://community.temporal.io/t/handling-testing-idempotency-in-java/18447>\
**Category:** Community Support\
**Tags:** java-sdk\
**Created:** [October 2, 2025, 4:12pm UTC](https://community.temporal.io/t/handling-testing-idempotency-in-java/18447 "2025-10-02T16:12:16Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![chrisw](https://avatars.discourse-cdn.com/v4/letter/c/919ad9/32.png) [@chrisw](https://community.temporal.io/u/chrisw)\
**Post date:** [October 2, 2025, 4:12pm UTC](https://community.temporal.io/t/handling-testing-idempotency-in-java/18447/1 "2025-10-02T16:12:16Z")

</div>

I have a simple activity and am looking to ensure its idempotent, for that I will use the following:

String idempotencyKey = info.getRunId() + “-” + info.getActivityId();

Persisted in a database as a primary key and checked for each execution of the activity.

In the example code: [What Is Idempotency? Why It Matters for Durable Systems | Temporal](https://temporal.io/blog/idempotency-and-durable-execution)

if ExternalAPI.UserExists(userData.Email) {  
return User{}, temporal.NewApplicationError(“Can’t create user: email already exists”, “UserAlreadyExists”)  
}

What should the java equivalent be here? How do we distinguish errors in an activity that result in the activity being retried, Vs an idempotency error which results in the workflow continuing to the next step? How would we test that in some form of integration test to see the different behaviour?

At the moment i’ve no way to trigger a replay of an activity to validate that idempotency handling works.

---

<div class="post-metadata">

**Author:** ![tihomir](https://sea2.discourse-cdn.com/flex016/user_avatar/community.temporal.io/tihomir/32/6580_2.png) [@tihomir](https://community.temporal.io/u/tihomir)\
**Post date:** [October 14, 2025, 2:43pm UTC](https://community.temporal.io/t/handling-testing-idempotency-in-java/18447/2 "2025-10-14T14:43:13Z")

</div>

> What should the java equivalent be here?

You can define a custom exception type, for example UserAlreadyExistsException and in activity options set it as a non-retryable failure, for example:

```auto
ActivityOptions.newBuilder()
    // ...
    .setRetryOptions(RetryOptions.newBuilder()
            .setDoNotRetry(UserAlreadyExistsException.class.getName())
            .build())
    .build());

```

in your workflow code then you can handle ActivityFailure and check its cause to see type, for example:

```auto
try {
  activities.createUser(User user);
} catch (ActivityFailure activityFailure) {
  if (activityFailure.getCause() instanceof ApplicationFailure) {
    ApplicationFailure af = (ApplicationFailure) activityFailure.getCause();
    // af.getMessage();
    // af.getType(); --> would be UserAlreadyExistsException
  } else if (activityFailure.getCause() instanceof TimeoutFailure) {
    TimeoutFailure tf = (TimeoutFailure) activityFailure.getCause();
    // tf.getTimeoutType(); -- can be schedule_to_start/schedule_to_close
  }
}

```

In case of a timeout, meaning activity timed out on last attempt, means your code maybe never executed, so maybe best to invoke activity again which would check if it was actually ever created.

Alternatively you could from activity code throw a non-retryable application failure with cause:

```auto
throw ApplicationFailure.newNonRetryableFailureWithCause(...);

```

where you can specify details that can later on inspect in workflow code

```auto
// ...
if (activityFailure.getCause() instanceof ApplicationFailure) {
  ApplicationFailure af = (ApplicationFailure) activityFailure.getCause();
  // af.getDetails();

}

```

---

<div class="post-metadata">

**Author:** ![chrisw](https://avatars.discourse-cdn.com/v4/letter/c/919ad9/32.png) [@chrisw](https://community.temporal.io/u/chrisw)\
**Post date:** [October 23, 2025, 9:14am UTC](https://community.temporal.io/t/handling-testing-idempotency-in-java/18447/3 "2025-10-23T09:14:25Z")

</div>

Thanks tihomir, very clear. I think what had confused me was how replay would handle activity invocation, i.e. when a workflow is replayed i was worried that it would invoke the activities that had already run, but as i understand it, it will not do that. It will use the stored history from the activities and get the workflow back to where it was running.

I guess the idempotency use case is more focused on activity retries rather than a full workflow replay?
