Skip to content

feat(java-sdk): implement individual tuple error handling for non-tra… #573

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

Conversation

curfew-marathon
Copy link
Contributor

@curfew-marathon curfew-marathon commented Jul 31, 2025

Addresses GitHub issue openfga/java-sdk#99 by implementing proper error handling for non-transaction writes to match JavaScript and .NET SDK behavior.

Key Changes:

New Model Classes:

  • Add ClientWriteStatus enum with SUCCESS/FAILURE values
  • Add ClientWriteSingleResponse class for individual tuple write results
  • Extend ClientWriteResponse with new constructor supporting detailed results

Core Logic Updates:

  • Completely rewrite writeNonTransaction method with parallel processing
  • Implement individual error isolation per chunk using CompletableFuture.allOf()
  • Add authentication error re-throwing to maintain existing behavior
  • Process writes and deletes in parallel chunks for improved performance

Error Handling Improvements:

  • Individual tuple failures no longer stop entire operation
  • Each tuple gets SUCCESS/FAILURE status with optional error details
  • Authentication errors (FgaApiAuthenticationError) are properly re-thrown
  • Non-authentication errors are captured per tuple without stopping others

Test Coverage:

  • Add comprehensive unit tests for new model classes
  • Update OpenFgaClientTest with mixed success/failure scenarios
  • Test authentication error handling and individual tuple status tracking

Configuration Updates:

  • Register new template files in config.overrides.json
  • Maintain backward compatibility with existing API signatures

Technical Details:

The implementation uses CompletableFuture.allOf() for parallel chunk processing, with .exceptionally() handlers that distinguish between authentication errors (which should halt execution) and other errors (which should be captured per tuple).

Empty chunk handling is properly implemented to avoid null pointer exceptions, and the chunksOf utility method handles edge cases correctly.

Backward Compatibility:

All existing API signatures are preserved. The new detailed response format is available through the new ClientWriteResponse constructor while maintaining the legacy ApiResponse-based constructor for transaction-based writes.

Resolves: openfga/java-sdk#99

Description

What problem is being solved?

How is it being solved?

What changes are made to solve it?

References

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

Summary by CodeRabbit

  • New Features

    • Added support for non-transactional write operations in the Java SDK client, allowing partial successes and failures to be reported per tuple.
    • Enhanced write response structure to provide detailed status for each write and delete operation.
  • Improvements

    • Expanded and clarified documentation for write methods, outlining transactional and non-transactional behaviors.
  • Bug Fixes

    • Improved error handling and reporting for non-transactional writes, ensuring all tuple results are included in the response.
  • Tests

    • Added comprehensive unit tests for new response models and write status handling.
    • Updated existing tests to validate the new per-tuple response structure and error reporting.

…nsaction writes

Addresses GitHub issue #99 by implementing proper error handling for non-transaction
writes to match JavaScript and .NET SDK behavior.

### Key Changes:

**New Model Classes:**
- Add ClientWriteStatus enum with SUCCESS/FAILURE values
- Add ClientWriteSingleResponse class for individual tuple write results
- Extend ClientWriteResponse with new constructor supporting detailed results

**Core Logic Updates:**
- Completely rewrite writeNonTransaction method with parallel processing
- Implement individual error isolation per chunk using CompletableFuture.allOf()
- Add authentication error re-throwing to maintain existing behavior
- Process writes and deletes in parallel chunks for improved performance

**Error Handling Improvements:**
- Individual tuple failures no longer stop entire operation
- Each tuple gets SUCCESS/FAILURE status with optional error details
- Authentication errors (FgaApiAuthenticationError) are properly re-thrown
- Non-authentication errors are captured per tuple without stopping others

**Test Coverage:**
- Add comprehensive unit tests for new model classes
- Update OpenFgaClientTest with mixed success/failure scenarios
- Test authentication error handling and individual tuple status tracking

**Configuration Updates:**
- Register new template files in config.overrides.json
- Maintain backward compatibility with existing API signatures

### Technical Details:

The implementation uses CompletableFuture.allOf() for parallel chunk processing,
with .exceptionally() handlers that distinguish between authentication errors
(which should halt execution) and other errors (which should be captured per tuple).

Empty chunk handling is properly implemented to avoid null pointer exceptions,
and the chunksOf utility method handles edge cases correctly.

### Backward Compatibility:

All existing API signatures are preserved. The new detailed response format
is available through the new ClientWriteResponse constructor while maintaining
the legacy ApiResponse-based constructor for transaction-based writes.

Resolves: openfga/java-sdk#99
Copy link

linux-foundation-easycla bot commented Jul 31, 2025

CLA Signed

The committers listed above are authorized under a signed CLA.

Copy link
Contributor

coderabbitai bot commented Jul 31, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This change introduces non-transactional write support to the Java SDK client, enabling per-tuple error handling and status reporting. It adds new model classes and enums to represent individual write results and statuses, implements chunked parallel writes with granular error wrapping, updates the response structure, and enhances related tests and documentation.

Changes

Cohort / File(s) Change Summary
Non-transactional Write Implementation & Refactor
config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache
Adds non-transactional write mode, detailed JavaDoc, chunked parallel write logic, granular error handling, and updates to transactional write response construction.
Model Enhancements for Write Responses
config/clients/java/template/src/main/api/client/model/ClientWriteResponse.java.mustache, config/clients/java/template/src/main/api/client/model/ClientWriteSingleResponse.java.mustache, config/clients/java/template/src/main/api/client/model/ClientWriteStatus.java.mustache
Adds new fields and constructors to ClientWriteResponse, introduces ClientWriteSingleResponse class for per-tuple results, and defines ClientWriteStatus enum for success/failure.
Test Updates and Additions
config/clients/java/template/src/test/api/client/OpenFgaClientTest.java.mustache, config/clients/java/template/src/test/api/client/model/ClientWriteSingleResponseTest.java.mustache, config/clients/java/template/src/test/api/client/model/ClientWriteStatusTest.java.mustache
Updates existing tests for new response structure and error handling, and adds unit tests for new model classes and enums.
Build Configuration
config/clients/java/template/settings.gradle.mustache
Adds a pluginManagement block specifying plugin repositories for Gradle builds.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant OpenFgaClient
    participant FGA_API

    Client->>OpenFgaClient: write(request, options)
    alt Transactional mode (default)
        OpenFgaClient->>FGA_API: POST /write (all tuples)
        alt Success
            FGA_API-->>OpenFgaClient: 200 OK
            OpenFgaClient-->>Client: ClientWriteResponse (all tuples SUCCESS)
        else Failure
            FGA_API-->>OpenFgaClient: Error
            OpenFgaClient-->>Client: Exception thrown
        end
    else Non-transactional mode
        loop For each chunk of tuples
            OpenFgaClient->>FGA_API: POST /write (chunk)
            alt Chunk Success
                FGA_API-->>OpenFgaClient: 200 OK
                OpenFgaClient-->>OpenFgaClient: Mark chunk tuples as SUCCESS
            else Chunk Failure
                FGA_API-->>OpenFgaClient: Error
                OpenFgaClient-->>OpenFgaClient: Mark chunk tuples as FAILURE, wrap error
            end
        end
        OpenFgaClient-->>Client: ClientWriteResponse (per-tuple SUCCESS/FAILURE)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Assessment against linked issues

Objective Addressed Explanation
Non-transactional write should catch errors per tuple and wrap them in response (#99)
Non-transactional write should not throw for single tuple validation errors (#99)
Non-transactional write should return per-tuple status (success/failure) with error info (#99)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Addition of pluginManagement block in Gradle settings template (settings.gradle.mustache) This build configuration change is unrelated to the non-transactional write error handling objective.
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/java-sdk-non-transaction-write-error-handling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

…l writes

- Update writeNonTransaction method to properly isolate chunk failures
- Failed chunks now mark tuples as FAILURE while allowing other chunks to continue processing
- Authentication errors are re-thrown to stop all processing as expected
- Matches .NET SDK behavior where 'some will pass some will fail' for non-transactional operations
- Each chunk processes independently with proper error boundary isolation
- Resolves issue #99 for Java SDK non-transaction write error handling

This ensures that individual chunk failures don't affect the processing of other chunks,
allowing for proper partial success scenarios in non-transactional write operations.
- Fix compilation errors by replacing incorrect toTupleKey() calls
- Use asTupleKey() for ClientTupleKey objects
- Use asTupleKeyWithoutCondition() for ClientTupleKeyWithoutCondition objects
- Fixes all 8 compilation errors in transaction-based writes, chunk processing, and utility methods
- Resolves PR build failures while maintaining correct error handling logic
- Replace asTupleKeyWithoutCondition() calls with direct TupleKey creation
- ClientWriteSingleResponse constructor expects TupleKey, not TupleKeyWithoutCondition
- Create TupleKey objects manually using user, relation, and object fields
- Fixes type compatibility errors in delete chunk processing and utility methods
- Resolves compilation failures while preserving error handling logic
The Issue99 reproduction test was accidentally added to the template during development.
This test was causing failures in PR builds and was not intended to be part of the
permanent test suite. The test has been removed to ensure clean PR integration.
Adds detailed documentation explaining:
- Transactional vs non-transactional modes
- Success and failure scenarios for each mode
- Exception handling behavior
- Caller responsibilities for error handling
- Implementation details for non-transactional error isolation

This addresses the need for clear documentation of the complex error
handling behavior introduced for non-transactional writes.
@curfew-marathon curfew-marathon marked this pull request as ready for review August 10, 2025 13:08
@curfew-marathon curfew-marathon requested a review from a team as a code owner August 10, 2025 13:08
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (6)
config/clients/java/template/src/main/api/client/model/ClientWriteStatus.java.mustache (1)

4-22: Add Jackson annotations for stable JSON (de)serialization

If this enum is ever serialized/deserialized (e.g., in responses), prefer explicit @JsonValue/@JsonCreator to ensure "success"/"failure" round-trip, independent of ObjectMapper settings.

 {{>licenseInfo}}
 package {{clientPackage}}.model;

+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
 
 public enum ClientWriteStatus {
     SUCCESS("success"),
     FAILURE("failure");
 
     private final String value;
 
     ClientWriteStatus(String value) {
         this.value = value;
     }
 
+    @JsonValue
     public String getValue() {
         return value;
     }
 
+    @JsonCreator
+    public static ClientWriteStatus fromValue(String v) {
+        for (ClientWriteStatus s : values()) {
+            if (s.value.equalsIgnoreCase(v)) {
+                return s;
+            }
+        }
+        throw new IllegalArgumentException("Unknown ClientWriteStatus: " + v);
+    }
+
     @Override
     public String toString() {
         return value;
     }
 }
config/clients/java/template/src/main/api/client/model/ClientWriteSingleResponse.java.mustache (1)

6-32: Use Throwable (not Exception) and add small ergonomics helpers

  • Using Exception forces wrapping of non-Exception throwables and loses fidelity. Prefer Throwable to carry original causes.
  • Add isSuccess()/isFailure() to reduce enum comparisons at call sites.
-public class ClientWriteSingleResponse {
+public class ClientWriteSingleResponse {
     private final TupleKey tupleKey;
     private final ClientWriteStatus status;
-    private final Exception error;
+    private final Throwable error;

-    public ClientWriteSingleResponse(TupleKey tupleKey, ClientWriteStatus status) {
+    public ClientWriteSingleResponse(TupleKey tupleKey, ClientWriteStatus status) {
         this(tupleKey, status, null);
     }

-    public ClientWriteSingleResponse(TupleKey tupleKey, ClientWriteStatus status, Exception error) {
+    public ClientWriteSingleResponse(TupleKey tupleKey, ClientWriteStatus status, Throwable error) {
         this.tupleKey = tupleKey;
         this.status = status;
         this.error = error;
     }

     public TupleKey getTupleKey() {
         return tupleKey;
     }

     public ClientWriteStatus getStatus() {
         return status;
     }

-    public Exception getError() {
+    public Throwable getError() {
         return error;
     }
+
+    public boolean isSuccess() { return status == ClientWriteStatus.SUCCESS; }
+    public boolean isFailure() { return status == ClientWriteStatus.FAILURE; }
 }
config/clients/java/template/src/test/api/client/OpenFgaClientTest.java.mustache (1)

1313-1330: Strengthen assertions for non-transaction failure details

Add checks for:

  • Response status code (should be 200 for non-transaction mode).
  • Error type on the failed tuple (e.g., FgaApiValidationError) to ensure correct mapping.
         // Verify response structure
         assertEquals(3, response.getWrites().size());
         assertEquals(0, response.getDeletes().size());
+        assertEquals(200, response.getStatusCode());

         // Check individual tuple statuses
         var writes = response.getWrites();
         assertEquals(ClientWriteStatus.SUCCESS, writes.get(0).getStatus());
         assertEquals(firstUser, writes.get(0).getTupleKey().getUser());
         assertNull(writes.get(0).getError());

         assertEquals(ClientWriteStatus.FAILURE, writes.get(1).getStatus());
         assertEquals(failedUser, writes.get(1).getTupleKey().getUser());
         assertNotNull(writes.get(1).getError());
+        assertTrue(writes.get(1).getError() instanceof FgaApiValidationError);

         assertEquals(ClientWriteStatus.SUCCESS, writes.get(2).getStatus());
         assertEquals(thirdUser, writes.get(2).getTupleKey().getUser());
         assertNull(writes.get(2).getError());
config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache (3)

397-441: Doc nit: clarify retry semantics scope

Minor: in “Caller Responsibilities” for non-transactional mode, consider a brief note that retries should be issued only for tuples marked FAILURE, not re-sending entire chunks.


548-575: DRY the write/delete chunk handling with a small helper

The success/failure mapping logic is duplicated for writes and deletes. Extract a private helper that:

  • Accepts the tuple list, a request builder (writes/deletes), and a TupleKey mapper.
  • Returns CompletableFuture<List<ClientWriteSingleResponse>>.

This reduces cognitive load and maintenance surface.

Also applies to: 578-611


535-541: Avoid mutating caller-provided options (optional)

You mutate writeOptions by inserting headers. Consider copying headers into a local ConfigurationOverride instead of modifying the options instance to prevent side effects when callers reuse it.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69978f0 and e75b6a4.

📒 Files selected for processing (9)
  • config/clients/java/config.overrides.json (2 hunks)
  • config/clients/java/template/settings.gradle.mustache (1 hunks)
  • config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache (6 hunks)
  • config/clients/java/template/src/main/api/client/model/ClientWriteResponse.java.mustache (2 hunks)
  • config/clients/java/template/src/main/api/client/model/ClientWriteSingleResponse.java.mustache (1 hunks)
  • config/clients/java/template/src/main/api/client/model/ClientWriteStatus.java.mustache (1 hunks)
  • config/clients/java/template/src/test/api/client/OpenFgaClientTest.java.mustache (5 hunks)
  • config/clients/java/template/src/test/api/client/model/ClientWriteSingleResponseTest.java.mustache (1 hunks)
  • config/clients/java/template/src/test/api/client/model/ClientWriteStatusTest.java.mustache (1 hunks)
🔇 Additional comments (10)
config/clients/java/template/settings.gradle.mustache (1)

1-6: LGTM: plugin repositories configured

Adding gradlePluginPortal() and mavenCentral() under pluginManagement is correct and commonly needed for plugin resolution.

config/clients/java/template/src/test/api/client/model/ClientWriteStatusTest.java.mustache (1)

9-19: LGTM: covers getValue() and toString()

The assertions validate both accessors for SUCCESS/FAILURE.

config/clients/java/config.overrides.json (2)

184-191: Registered new client model templates

ClientWriteSingleResponse and ClientWriteStatus entries look correct (paths and destination filenames align with clientPackage/model).


440-447: Registered new test templates

ClientWriteStatusTest and ClientWriteSingleResponseTest entries are correct and will ensure coverage is generated.

config/clients/java/template/src/test/api/client/model/ClientWriteSingleResponseTest.java.mustache (1)

12-16: Unable to locate TupleKey template—please verify setter signature

I couldn’t find a TupleKey.java.mustache under config/clients/java/template/src/main/api/client/model/. Please confirm whether the generated TupleKey builder defines:

public TupleKey object(String object)
rather than
public TupleKey _object(String _object)

Steps to validate:

  • Locate the template file for TupleKey in config/clients/java/template/src/main/api/client/model/.
  • Open it and check the fluent‐setter method declaration.
config/clients/java/template/src/test/api/client/OpenFgaClientTest.java.mustache (1)

1147-1154: LGTM: validates the new per-tuple results for transactional writes

Solid assertions on writes/deletes sizes, status, user, and error nullity.

config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache (4)

345-387: Great Javadoc clarifying dual-mode behavior

Clear, actionable documentation of transactional vs non-transactional semantics, including caller responsibilities. This will reduce misuse.


483-499: Transactional path correctly constructs per-tuple success results

Mapping request tuples to SUCCESS results on a 2xx response is consistent with atomic behavior and aligns with tests.


613-632: All-of reduction preserves input order; good

Collecting chunk futures in input order and joining post allOf preserves tuple order across parallel execution. The final aggregation is correct.


683-689: Utility methods correctly return per-tuple SUCCESS responses

writeTuples and deleteTuples now return detailed SUCCESS results which aligns with the new response shape.

Also applies to: 723-731

…uples

- Fix .apiTokenIssuer to use FGA_API_TOKEN_ISSUER instead of FGA_TOKEN_ISSUER
- Add duplicate tuple writes to demonstrate individual error handling in non-transactional mode
- Add mapping for settings.gradle.mustache in config.overrides.json
- Ensures settings.gradle file is generated with pluginManagement block
- Addresses Code Rabbit suggestion for complete Gradle project structure
Copy link
Contributor

@jimmyjames jimmyjames left a comment

Choose a reason for hiding this comment

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

Looks good! Nice update of the docs and example, and fixing the example as well ❤️

@curfew-marathon curfew-marathon added this pull request to the merge queue Aug 13, 2025
Merged via the queue into main with commit 162d7ca Aug 13, 2025
19 checks passed
@curfew-marathon curfew-marathon deleted the feature/java-sdk-non-transaction-write-error-handling branch August 13, 2025 17:37
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.

In the non-Txn Write, we should be catching the errors and wrapping them
3 participants