Skip to content

Add support for idempotency keys when creating tickets#808

Open
aleksei-averchenko-wise wants to merge 16 commits intocloudbees-oss:masterfrom
aleksei-averchenko-wise:create-ticket-idempotency-key
Open

Add support for idempotency keys when creating tickets#808
aleksei-averchenko-wise wants to merge 16 commits intocloudbees-oss:masterfrom
aleksei-averchenko-wise:create-ticket-idempotency-key

Conversation

@aleksei-averchenko-wise
Copy link
Copy Markdown

@aleksei-averchenko-wise aleksei-averchenko-wise commented Mar 19, 2026

This change implements support for Zendesk's idempotency key
feature
when creating tickets. I happen to need to use this feature myself :)

I added two new API methods: createTicketIdempotent and createTicketIdempotentAsync. See the usage example in the updated README.md.

Copy link
Copy Markdown
Collaborator

@PierreBtz PierreBtz left a comment

Choose a reason for hiding this comment

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

Hey! Thanks for the contribution. I did not have time yet to look into it in details, but I wonder if we could maybe improve the architecture.

What you suggest is to add the idempotency fields as part of the Ticket model, which seems to mix two different concerns:

  • the ticket model itself
  • the idempotency feature, which is not really part of the Ticket model but a feature of the ticket creation flow

As a consequence of this choice, it means that stored tickets/serialized ticket will keep those fields which have no real value after the ticket creation flow is done (if I have a null what does that mean exactly?).

A potential approach to this problem would be to avoid changing the Ticket model (keep it aligned with the ZD one) and create a TicketResult or even a ZendeskResult<T> class if we want to be future proof that would contain both the model and the additional feature of the creation flow, so roughtly:

  public class CreateResult<T> {

    private final T entity;
    private final String idempotencyKey;
    private final boolean idempotencyHit;

// getters setters...
// we really need to bump from java 11 to have records...
}

Then you would keep the current createTicket methods and create new methods:

  public CreateResult<Ticket> createTicketIdempotent(Ticket ticket, String idempotencyKey) {
    return complete(createTicketIdempotentAsync(ticket, idempotencyKey));
  }

  public ListenableFuture<CreateResult<Ticket>> createTicketIdempotentAsync(
      Ticket ticket, String idempotencyKey) {
    // create the request
    // header addition
    // completion handler specific to idempotent feature
    // submit
  }

and so on...

Call for end user would look like:

  CreateResult<Ticket> result = zendesk.createTicketIdempotent(ticket, "key-123");
  Ticket created = result.getEntity();
  if (result.isIdempotencyHit()) { 
  // do stuff 
}

@aleksei-averchenko-wise
Copy link
Copy Markdown
Author

A potential approach to this problem would be to avoid changing the Ticket model (keep it aligned with the ZD one) and create a TicketResult or even a ZendeskResult<T> class if we want to be future proof that would contain both the model and the additional feature of the creation flow

Sounds good! I thought of that initially but then decided to be clever and try and sneak the feature into existing calls to make adoption easier on my end. I'll redo it the way you suggested.

@aleksei-averchenko-wise
Copy link
Copy Markdown
Author

aleksei-averchenko-wise commented Apr 8, 2026

@PierreBtz a couple of notes:

  • I added test cases in RealSmokeTest, but I don't have access to your sandbox account. Can you run it to verify that the tests still pass, please?
  • In Javadocs, I added @since 1.5.0, because that's what I assume the new release is going to be called. I haven't bumped the version in pom.xml though because I saw in git blame that you tend to do it separately. Let me know if I should change anything in this regard.
  • I took the liberty of adding AGENTS.md because Claude Code kept stumbling when trying to run Maven with my system default version of Java. I can remove it if you don't like it :)

I tried to follow the contribution guidelines to the extent that the existing code follows them, hope that's good enough.

Thanks!

@aleksei-averchenko-wise
Copy link
Copy Markdown
Author

I updated README.md several times to make sure that the usage example is as simple as it can be. Should be alright now.

@aleksei-averchenko-wise
Copy link
Copy Markdown
Author

I'm truly finished with README.md now. Ideally we should have someone from Zendesk take a look at it for sanity checking, but I don't think it's strictly necessary.

Copy link
Copy Markdown
Collaborator

@PierreBtz PierreBtz left a comment

Choose a reason for hiding this comment

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

Sorry I didn't have time to completely review this this week. I'll do it next week.

I much prefer this iteration over the last one, thanks for doing the changes.

A couple of on going comments already though:

For now I'll trigger the CI to see if the new tests pass against our sandbox.

@aleksei-averchenko-wise
Copy link
Copy Markdown
Author

aleksei-averchenko-wise commented Apr 13, 2026

@PierreBtz I think the Java 8 stuff in AGENTS.md is correct, I've just tested that it indeed does what Claude said. It refers to this section of pom.xml:

                <checkSignatureRule implementation="org.codehaus.mojo.animal_sniffer.enforcer.CheckSignatureRule">
                  <signature>
                    <groupId>org.codehaus.mojo.signature</groupId>
                    <artifactId>java18</artifactId>
                    <version>1.0</version>
                  </signature>
                </checkSignatureRule>

I think what it does is it tries to validate the compiled bytecode against various JVM versions, as opposed to validating the Java source code itself. So even though we use lambdas left and right, it's okay because they get compiled away.

I checked that java18 really does mean Java 8 by inserting code that uses a Java 9 feature:

    VarHandle longArrayViewHandle = MethodHandles.byteArrayViewVarHandle(
        long[].class, java.nio.ByteOrder.BIG_ENDIAN);

(see https://en.wikipedia.org/wiki/Java_version_history#Java_SE_9 and https://openjdk.org/jeps/193). The compilation (JAVA_HOME=$(/usr/libexec/java_home -v 11) mvn verify) did fail as expected:

[INFO] --- enforcer:3.6.2:enforce (check-java-compat) @ zendesk-java-client ---
[WARNING] ruleName checkSignatureRule with implementation org.codehaus.mojo.animal_sniffer.enforcer.CheckSignatureRuleuses the deprecated Maven Enforcer Plugin API. This will not be supported in a future version of the plugin. Please contact the rule maintainer to upgrade the rule implementation to the current API.
[INFO] Checking unresolved references to org.codehaus.mojo.signature:java18:1.0
[ERROR] /Users/aleksei.averchenko/dev/zendesk-java-client/src/main/java/org/zendesk/client/v2/IdempotencyUtil.java:74: Undefined reference: java.lang.invoke.VarHandle
[ERROR] /Users/aleksei.averchenko/dev/zendesk-java-client/src/main/java/org/zendesk/client/v2/IdempotencyUtil.java:74: Undefined reference: java.lang.invoke.VarHandle java.lang.invoke.MethodHandles.byteArrayViewVarHandle(Class, java.nio.ByteOrder)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.431 s
[INFO] Finished at: 2026-04-13T11:00:22+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-enforcer-plugin:3.6.2:enforce (check-java-compat) on project zendesk-java-client: 
[ERROR] Rule 0: org.codehaus.mojo.animal_sniffer.enforcer.CheckSignatureRule failed with message:
[ERROR] Signature errors found. Verify them and ignore them with the proper annotation if needed.
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException

BTW I don't think there are any newer signature versions in the Maven repository (https://mvnrepository.com/artifact/org.codehaus.mojo.signature), and there is a deprectation warning in the log above. So it might be worth removing this rule if JVM 8 bytecode compatibility isn't intended.

@aleksei-averchenko-wise
Copy link
Copy Markdown
Author

aleksei-averchenko-wise commented Apr 13, 2026

As for catching idempotency errors, my thinking was that isIdempotencyConflict already has very tight filtering so it can't misfire, so it's safe to include this check by default. Potential alternatives could be:

  1. Keep the existing code as is - but then the exception handling logic would become very fragmented and difficult to maintain.
  2. Introduce another flag to checkStatusCode - quite feasible, but forwarding it would add to the boilerplate, so I'm not sure that it's a good idea.
  3. Somehow move this check entirely to IdempotencyUtil - I'd love that, but unfortunately from what I can tell, there isn't an easy way to do that. onThrowable should probably not be used for this purpose: any exception that it throws is propagated as is instead of being wrapped in aCompletedFuture:
// org.asynchttpclient.DefaultAsyncHttpClient
private <T> ListenableFuture<T> execute(Request request, final AsyncHandler<T> asyncHandler) {
  try {
    return requestSender.sendRequest(request, asyncHandler, null);
  } catch (Exception e) {
    asyncHandler.onThrowable(e);
    return new ListenableFuture.CompletedFailure<>(e);
  }
}

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