-
Notifications
You must be signed in to change notification settings - Fork 242
Add support for idempotency keys when creating tickets #808
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
PierreBtz
merged 16 commits into
cloudbees-oss:master
from
aleksei-averchenko-wise:create-ticket-idempotency-key
Apr 15, 2026
Merged
Changes from 15 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
3727a90
Added support for `Idempotency-Key` when creating tickets
aleksei-averchenko-wise eb2114a
Added tests
aleksei-averchenko-wise 3975a39
Added a special exception subtype for idempotency conflicts
aleksei-averchenko-wise 517cd1f
Generated JavaDoc for the new code
aleksei-averchenko-wise 6f63773
Updated `README.md`
aleksei-averchenko-wise bc2272b
Removed `ZendeskResponseException$isIdempotencyConflict`
aleksei-averchenko-wise 4ee2fc6
Reworked the implementation following a PR code review
aleksei-averchenko-wise 59c218e
Ran `mvn spotless:apply`
aleksei-averchenko-wise 6423c43
Added `AGENTS.md` with instructions to pin the Java version with `JAV…
aleksei-averchenko-wise 26d61fe
Added test cases in `RealSmokeTest` for `createTicketIdempotent`
aleksei-averchenko-wise 13489db
[Claude] Modernised the code for Java 11
aleksei-averchenko-wise 5f361ee
Updated the example in README.md
aleksei-averchenko-wise 141bfbf
Changed the example in README.md to be completely stateless
aleksei-averchenko-wise 62564ac
Fixed a typo
aleksei-averchenko-wise e067228
Fixed the example in `README.md` (this time for sure)
aleksei-averchenko-wise 2ff6446
Fixed issues in `AGENTS.md`
aleksei-averchenko-wise File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| # Agent Instructions for zendesk-java-client | ||
|
|
||
| This document provides guidance for AI agents and developers working on this project. | ||
|
|
||
| ## Java Version Requirements | ||
|
|
||
| This project **compiles with Java 11** (as specified in `pom.xml` with `maven.compiler.source` and `maven.compiler.target` set to 11) but **must maintain Java 8 API compatibility**. | ||
|
|
||
| ### CRITICAL: Java 8 API Compatibility | ||
|
|
||
| **The project enforces Java 8 API compatibility via animal-sniffer, even though it compiles with Java 11.** | ||
|
|
||
| This means: | ||
| - ✅ **Allowed:** Java 8 language features (lambdas, method references, streams, Optional, etc.) | ||
| - ✅ **Allowed:** Java 11 compiler and build tools | ||
| - ❌ **NOT Allowed:** APIs added in Java 9+ (e.g., `Objects.requireNonNullElse()`, `List.of()`, `Map.of()`, `String.isBlank()`, etc.) | ||
|
|
||
| When modernizing code or adding features: | ||
| 1. Use Java 8 language syntax features freely (lambdas, streams, etc.) | ||
| 2. Only use APIs available in Java 8 (check the Javadoc version!) | ||
| 3. The animal-sniffer plugin will fail the build if you use Java 9+ APIs | ||
| 4. If in doubt, verify API availability at https://docs.oracle.com/javase/8/docs/api/ | ||
|
|
||
| ### Running Maven Commands | ||
|
|
||
| When running Maven commands, ensure you're using Java 11 by setting `JAVA_HOME`: | ||
|
|
||
| ```bash | ||
| JAVA_HOME=$(/usr/libexec/java_home -v 11) mvn <command> | ||
| ``` | ||
|
|
||
| ### Common Commands | ||
|
|
||
| **Build the project:** | ||
| ```bash | ||
| JAVA_HOME=$(/usr/libexec/java_home -v 11) mvn clean install | ||
|
aleksei-averchenko-wise marked this conversation as resolved.
Outdated
|
||
| ``` | ||
|
|
||
| **Run tests:** | ||
| ```bash | ||
| JAVA_HOME=$(/usr/libexec/java_home -v 11) mvn test | ||
| ``` | ||
|
|
||
| **Apply code formatting (Spotless):** | ||
| ```bash | ||
| JAVA_HOME=$(/usr/libexec/java_home -v 11) mvn spotless:apply | ||
| ``` | ||
|
|
||
| **Check code formatting:** | ||
| ```bash | ||
| JAVA_HOME=$(/usr/libexec/java_home -v 11) mvn spotless:check | ||
| ``` | ||
|
|
||
| ## Code Formatting | ||
|
|
||
| This project uses [Spotless](https://github.com/diffplug/spotless) with google-java-format for code formatting. | ||
|
|
||
| - All Java code must be formatted before committing | ||
| - Run `mvn spotless:apply` to format code automatically | ||
|
|
||
| ## Project Structure | ||
|
|
||
| - **Source code:** `src/main/java/org/zendesk/client/v2/` | ||
| - **Tests:** `src/test/java/org/zendesk/client/v2/` | ||
| - **Main entry point:** `Zendesk.java` - The primary API client class | ||
|
|
||
| ## Dependencies | ||
|
|
||
| Key dependencies include: | ||
| - async-http-client for HTTP operations | ||
| - Jackson for JSON serialization/deserialization | ||
| - SLF4J for logging | ||
| - JUnit 4 for testing | ||
| - WireMock for HTTP mocking in tests | ||
|
|
||
| ## Enforcement Rules | ||
|
|
||
| The project uses Maven Enforcer Plugin to ensure: | ||
| - Bytecode version matches Java 11 | ||
| - **API compatibility with Java 8 (via animal-sniffer)** - This is enforced at build time and will fail if Java 9+ APIs are used | ||
|
|
||
| ### Why Java 8 API Compatibility? | ||
|
|
||
| This library is designed to be usable by applications running on Java 8 JVMs, even though it's built with Java 11 tooling. This is a common pattern for libraries that want maximum compatibility while benefiting from modern build tools. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| AGENTS.md |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| package org.zendesk.client.v2; | ||
|
|
||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||
| import com.fasterxml.jackson.databind.JsonNode; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import org.asynchttpclient.AsyncCompletionHandler; | ||
| import org.asynchttpclient.RequestBuilder; | ||
| import org.asynchttpclient.Response; | ||
| import org.zendesk.client.v2.model.IdempotentResult; | ||
|
|
||
| /** | ||
| * Utility class for handling Zendesk API idempotency keys. | ||
| * | ||
| * <p>Provides methods to add idempotency headers to requests and process idempotency-related | ||
| * response headers. Supports the Zendesk API's idempotency feature which allows safe retries of | ||
| * create operations without creating duplicate resources. | ||
| * | ||
| * @see <a href="https://developer.zendesk.com/api-reference/ticketing/introduction/#idempotency"> | ||
| * Zendesk API Idempotency</a> | ||
| * @since 1.5.0 | ||
| */ | ||
| public class IdempotencyUtil { | ||
|
|
||
| static final String IDEMPOTENCY_KEY_HEADER = "Idempotency-Key"; | ||
| static final String IDEMPOTENCY_LOOKUP_HEADER = "x-idempotency-lookup"; | ||
| static final String IDEMPOTENCY_LOOKUP_HIT = "hit"; | ||
| static final String IDEMPOTENCY_LOOKUP_MISS = "miss"; | ||
| static final String IDEMPOTENCY_ERROR_NAME = "IdempotentRequestError"; | ||
|
|
||
| public static RequestBuilder addIdempotencyHeader(RequestBuilder builder, String idempotencyKey) { | ||
| // https://developer.zendesk.com/api-reference/ticketing/introduction/#idempotency | ||
| return builder.setHeader(IDEMPOTENCY_KEY_HEADER, idempotencyKey); | ||
| } | ||
|
|
||
| public static <T> AsyncCompletionHandler<IdempotentResult<T>> wrapHandler( | ||
| AsyncCompletionHandler<T> handler) { | ||
| return new AsyncCompletionHandler<>() { | ||
| @Override | ||
| public IdempotentResult<T> onCompleted(Response response) throws Exception { | ||
| T entity = handler.onCompleted(response); | ||
| boolean duplicateRequest = isDuplicateResponse(response); | ||
|
|
||
| return new IdempotentResult<>(entity, duplicateRequest); | ||
| } | ||
|
|
||
| @Override | ||
| public void onThrowable(Throwable t) { | ||
| handler.onThrowable(t); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| public static boolean isIdempotencyConflict(Response response, ObjectMapper mapper) | ||
| throws JsonProcessingException { | ||
| if (response.getStatusCode() != 400) { | ||
| return false; | ||
| } | ||
|
|
||
| // Note: Jackson's own docs are a bit outdated in that `readTree` returns | ||
| // `MissingNode.getInstance()` and not `null` when given an essentially empty string. | ||
| JsonNode error = mapper.readTree(response.getResponseBody()).path("error"); | ||
| return IDEMPOTENCY_ERROR_NAME.equals(error.textValue()); | ||
| } | ||
|
|
||
| private static boolean isDuplicateResponse(Response response) { | ||
| // https://developer.zendesk.com/api-reference/ticketing/introduction/#idempotency | ||
| String idempotencyLookup = response.getHeader(IDEMPOTENCY_LOOKUP_HEADER); | ||
| if (idempotencyLookup == null) { | ||
| idempotencyLookup = "<absent>"; | ||
| } | ||
|
|
||
| switch (idempotencyLookup) { | ||
| case IDEMPOTENCY_LOOKUP_HIT: | ||
| return true; | ||
| case IDEMPOTENCY_LOOKUP_MISS: | ||
| return false; | ||
| default: | ||
| throw new IllegalArgumentException( | ||
| String.format( | ||
| "Unexpected value of the idempotency lookup header: %s", idempotencyLookup)); | ||
| } | ||
| } | ||
|
|
||
| private IdempotencyUtil() { | ||
| throw new UnsupportedOperationException("Utility class"); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.