diff --git a/Makefile b/Makefile
index e2368e62..b2c30e12 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 # Main config
-OPENFGA_DOCKER_TAG = latest
+OPENFGA_DOCKER_TAG = v1.4.0-rc1
 OPEN_API_URL = https://raw.githubusercontent.com/openfga/api/main/docs/openapiv2/apidocs.swagger.json
 OPENAPI_GENERATOR_CLI_DOCKER_TAG = v6.4.0
 NODE_DOCKER_TAG = 18-alpine
diff --git a/config/clients/java/config.overrides.json b/config/clients/java/config.overrides.json
index 3ec726be..0897a7d5 100644
--- a/config/clients/java/config.overrides.json
+++ b/config/clients/java/config.overrides.json
@@ -31,6 +31,10 @@
   "allowUnicodeIdentifiers": true,
   "caseInsensitiveResponseHeaders": true,
   "files": {
+    "auth-model.json" : {
+      "destinationFilename": "src/test-integration/resources/auth-model.json",
+      "templateType": "SupportingFiles"
+    },
     "build.gradle.mustache" : {
       "destinationFilename": "build.gradle",
       "templateType": "SupportingFiles"
@@ -123,10 +127,18 @@
       "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientReadResponse.java",
       "templateType": "SupportingFiles"
     },
+    "client-ClientRelationshipCondition.java.mustache" : {
+      "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientRelationshipCondition.java",
+      "templateType": "SupportingFiles"
+    },
     "client-ClientTupleKey.java.mustache" : {
       "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientTupleKey.java",
       "templateType": "SupportingFiles"
     },
+    "client-ClientTupleKeyWithoutCondition.java.mustache" : {
+      "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientTupleKeyWithoutCondition.java",
+      "templateType": "SupportingFiles"
+    },
     "client-ClientWriteAssertionsResponse.java.mustache" : {
       "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientWriteAssertionsResponse.java",
       "templateType": "SupportingFiles"
diff --git a/config/clients/java/template/.github/dependabot.yaml b/config/clients/java/template/.github/dependabot.yaml
new file mode 100644
index 00000000..5a916f67
--- /dev/null
+++ b/config/clients/java/template/.github/dependabot.yaml
@@ -0,0 +1,18 @@
+version: 2
+updates:
+  - package-ecosystem: "gradle"
+    directory: "/"
+    schedule:
+      interval: "monthly"
+    groups:
+     dependencies:
+        patterns:
+          - "*"
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "monthly"
+    groups:
+     dependencies:
+        patterns:
+          - "*"
diff --git a/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache b/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache
index c172204f..0fb562b3 100644
--- a/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache
+++ b/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache
@@ -6,29 +6,40 @@ import static org.junit.jupiter.api.Assertions.*;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import {{invokerPackage}}.*;
-import {{modelPackage}}.*;
 import {{configPackage}}.*;
-import dev.openfga.errors.ApiException;
+import {{modelPackage}}.*;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.net.http.HttpClient;
 import java.util.List;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
 
+@TestInstance(Lifecycle.PER_CLASS)
 public class OpenFgaApiIntegrationTest {
     private static final ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
-    private static final String DEFAULT_AUTH_MODEL =
-            "{\"schema_version\":\"1.1\",\"type_definitions\":[{\"type\":\"user\"},{\"type\":\"document\",\"relations\":{\"reader\":{\"this\":{}},\"writer\":{\"this\":{}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}]}";
     private static final String DEFAULT_USER = "user:81684243-9356-4421-8fbf-a4f8d36aa31b";
     private static final String DEFAULT_DOC = "document:2021-budget";
-    public static final TupleKey DEFAULT_TUPLE_KEY =
+    private static final TupleKey DEFAULT_TUPLE_KEY =
             new TupleKey().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC);
-    public static final List<TupleKey> DEFAULT_TUPLE_KEYS = List.of(DEFAULT_TUPLE_KEY);
+    private static final List<TupleKey> DEFAULT_TUPLE_KEYS = List.of(DEFAULT_TUPLE_KEY);
+    private String authModelJson;
 
     private OpenFgaApi api;
 
+    @BeforeAll
+    public void loadAuthModelJson() throws IOException {
+        authModelJson = Files.readString(Paths.get("src", "test-integration", "resources", "auth-model.json"));
+    }
+
     @BeforeEach
     public void initializeApi() throws Exception {
+        System.setProperty("HttpRequestAttempt.debug-logging", "enable");
+
         Configuration apiConfig = new Configuration().apiUrl("http://localhost:8080");
         api = new OpenFgaApi(apiConfig);
     }
@@ -112,7 +123,7 @@ public class OpenFgaApiIntegrationTest {
         assertEquals(authModelId, authModel.getId());
         String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions());
         assertEquals(
-                "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]}}}}]",
+                "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"conditional_reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"name_starts_with_a\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]}}}}]",
                 typeDefsJson);
     }
 
@@ -136,7 +147,7 @@ public class OpenFgaApiIntegrationTest {
                         String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions());
 
                         assertEquals(
-                                "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]}}}}]",
+                                "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"conditional_reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"name_starts_with_a\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]}}}}]",
                                 typeDefsJson);
                     } catch (JsonProcessingException ex) {
                         assertNull(ex);
@@ -149,8 +160,7 @@ public class OpenFgaApiIntegrationTest {
         // Given
         String storeName = thisTestName();
         String storeId = createStore(storeName);
-        WriteAuthorizationModelRequest request =
-                mapper.readValue(DEFAULT_AUTH_MODEL, WriteAuthorizationModelRequest.class);
+        WriteAuthorizationModelRequest request = mapper.readValue(authModelJson, WriteAuthorizationModelRequest.class);
 
         // When
         WriteAuthorizationModelResponse response =
@@ -168,9 +178,9 @@ public class OpenFgaApiIntegrationTest {
         String storeName = thisTestName();
         String storeId = createStore(storeName);
         String _authModelId = writeAuthModel(storeId);
-        WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(List.of(DEFAULT_TUPLE_KEY)));
+        WriteRequest writeRequest = new WriteRequest().writes(new WriteRequestWrites().tupleKeys(List.of(DEFAULT_TUPLE_KEY)));
         ReadRequest readRequest =
-                new ReadRequest().tupleKey(new TupleKey().user(DEFAULT_USER)._object(DEFAULT_DOC));
+                new ReadRequest().tupleKey(new ReadRequestTupleKey().user(DEFAULT_USER)._object(DEFAULT_DOC));
 
         // When
         api.write(storeId, writeRequest).get();
@@ -189,9 +199,9 @@ public class OpenFgaApiIntegrationTest {
         String storeName = thisTestName();
         String storeId = createStore(storeName);
         String _authModelId = writeAuthModel(storeId);
-        WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(DEFAULT_TUPLE_KEYS));
+        WriteRequest writeRequest = new WriteRequest().writes(new WriteRequestWrites().tupleKeys(DEFAULT_TUPLE_KEYS));
         CheckRequest checkRequest = new CheckRequest()
-                .tupleKey(new TupleKey().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC));
+                .tupleKey(new CheckRequestTupleKey().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC));
 
         // When
         api.write(storeId, writeRequest).get();
@@ -207,9 +217,9 @@ public class OpenFgaApiIntegrationTest {
         String storeName = thisTestName();
         String storeId = createStore(storeName);
         String _authModelId = writeAuthModel(storeId);
-        WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(DEFAULT_TUPLE_KEYS));
+        WriteRequest writeRequest = new WriteRequest().writes(new WriteRequestWrites().tupleKeys(DEFAULT_TUPLE_KEYS));
         ExpandRequest expandRequest =
-                new ExpandRequest().tupleKey(new TupleKey()._object(DEFAULT_DOC).relation("reader"));
+                new ExpandRequest().tupleKey(new ExpandRequestTupleKey()._object(DEFAULT_DOC).relation("reader"));
 
         // When
         api.write(storeId, writeRequest).get();
@@ -229,7 +239,7 @@ public class OpenFgaApiIntegrationTest {
         String storeName = thisTestName();
         String storeId = createStore(storeName);
         String _authModelId = writeAuthModel(storeId);
-        WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(DEFAULT_TUPLE_KEYS));
+        WriteRequest writeRequest = new WriteRequest().writes(new WriteRequestWrites().tupleKeys(DEFAULT_TUPLE_KEYS));
         ListObjectsRequest listObjectsRequest =
                 new ListObjectsRequest().user(DEFAULT_USER).relation("reader").type("document");
 
@@ -249,7 +259,7 @@ public class OpenFgaApiIntegrationTest {
         String storeName = thisTestName();
         String storeId = createStore(storeName);
         String _authModelId = writeAuthModel(storeId);
-        WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(DEFAULT_TUPLE_KEYS));
+        WriteRequest writeRequest = new WriteRequest().writes(new WriteRequestWrites().tupleKeys(DEFAULT_TUPLE_KEYS));
 
         // When
         api.write(storeId, writeRequest).get();
@@ -261,7 +271,7 @@ public class OpenFgaApiIntegrationTest {
         String firstTupleKeyJson =
                 mapper.writeValueAsString(response.getChanges().get(0).getTupleKey());
         assertEquals(
-                "{\"object\":\"document:2021-budget\",\"relation\":\"reader\",\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\"}",
+                "{\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\",\"relation\":\"reader\",\"object\":\"document:2021-budget\",\"condition\":null}",
                 firstTupleKeyJson);
     }
 
@@ -272,7 +282,12 @@ public class OpenFgaApiIntegrationTest {
         String storeId = createStore(storeName);
         String authModelId = writeAuthModel(storeId);
         WriteAssertionsRequest writeRequest = new WriteAssertionsRequest()
-                .assertions(List.of(new Assertion().tupleKey(DEFAULT_TUPLE_KEY).expectation(true)));
+                .assertions(List.of(new Assertion()
+                        .tupleKey(new CheckRequestTupleKey()
+                                .user(DEFAULT_USER)
+                                .relation("reader")
+                                ._object(DEFAULT_DOC))
+                        .expectation(true)));
 
         // When
         api.writeAssertions(storeId, authModelId, writeRequest).get();
@@ -282,7 +297,7 @@ public class OpenFgaApiIntegrationTest {
         // Then
         String responseJson = mapper.writeValueAsString(response.getAssertions());
         assertEquals(
-                "[{\"tuple_key\":{\"object\":\"document:2021-budget\",\"relation\":\"reader\",\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\"},\"expectation\":true}]",
+                "[{\"tuple_key\":{\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\",\"relation\":\"reader\",\"object\":\"document:2021-budget\"},\"expectation\":true}]",
                 responseJson);
     }
 
@@ -303,10 +318,8 @@ public class OpenFgaApiIntegrationTest {
      * @return The created Authorization Model ID
      */
     private String writeAuthModel(String storeId) throws Exception {
-        WriteAuthorizationModelRequest request =
-                mapper.readValue(DEFAULT_AUTH_MODEL, WriteAuthorizationModelRequest.class);
-        WriteAuthorizationModelResponse response =
-                api.writeAuthorizationModel(storeId, request).get().getData();
+        var request = mapper.readValue(authModelJson, WriteAuthorizationModelRequest.class);
+        var response = api.writeAuthorizationModel(storeId, request).get().getData();
         return response.getAuthorizationModelId();
     }
 
diff --git a/config/clients/java/template/OpenFgaApiTest.java.mustache b/config/clients/java/template/OpenFgaApiTest.java.mustache
index 4c9b73b0..c69447c4 100644
--- a/config/clients/java/template/OpenFgaApiTest.java.mustache
+++ b/config/clients/java/template/OpenFgaApiTest.java.mustache
@@ -14,7 +14,9 @@ import {{configPackage}}.*;
 import {{errorsPackage}}.*;
 import java.net.http.HttpClient;
 import java.time.Duration;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -44,7 +46,10 @@ public class OpenFgaApiTest {
 
     @BeforeEach
     public void beforeEachTest() throws Exception {
+        System.setProperty("HttpRequestAttempt.debug-logging", "enable");
+
         mockHttpClient = new HttpClientMock();
+        mockHttpClient.debugOn();
 
         mockHttpClientBuilder = mock(HttpClient.Builder.class);
         when(mockHttpClientBuilder.executor(any())).thenReturn(mockHttpClientBuilder);
@@ -560,7 +565,7 @@ public class OpenFgaApiTest {
         // Given
         String postUrl = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/authorization-models";
         String expectedBody =
-                "{\"type_definitions\":[{\"type\":\"document\",\"relations\":{},\"metadata\":null}],\"schema_version\":\"1.1\"}";
+                "{\"type_definitions\":[{\"type\":\"document\",\"relations\":{},\"metadata\":null}],\"schema_version\":\"1.1\",\"conditions\":{}}";
         String responseBody = String.format("{\"authorization_model_id\":\"%s\"}", DEFAULT_AUTH_MODEL_ID);
         mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(201, responseBody);
         WriteAuthorizationModelRequest request = new WriteAuthorizationModelRequest()
@@ -793,8 +798,8 @@ public class OpenFgaApiTest {
         // Given
         String getPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/changes";
         String responseBody = String.format(
-                "{\"changes\":[{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}}]}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"changes\":[{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}}]}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient.onGet(getPath).doReturn(200, responseBody);
         String type = null; // Input is optional
         Integer pageSize = null; // Input is optional
@@ -909,14 +914,57 @@ public class OpenFgaApiTest {
         // Given
         String postUrl = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/read";
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"page_size\":null,\"continuation_token\":null}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"page_size\":null,\"continuation_token\":null}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         String responseBody = String.format(
                 "{\"tuples\":[{\"key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}}]}",
                 DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, responseBody);
         ReadRequest request = new ReadRequest()
-                .tupleKey(new TupleKey()
+                .tupleKey(new ReadRequestTupleKey()
+                        ._object(DEFAULT_OBJECT)
+                        .relation(DEFAULT_RELATION)
+                        .user(DEFAULT_USER));
+
+        // When
+        var response = fga.read(DEFAULT_STORE_ID, request).get();
+
+        // Then
+        mockHttpClient.verify().post(postUrl).withBody(is(expectedBody)).called(1);
+        assertNotNull(response.getData());
+        assertNotNull(response.getData().getTuples());
+        assertEquals(1, response.getData().getTuples().size());
+        var key = response.getData().getTuples().get(0).getKey();
+        assertNotNull(key);
+        assertEquals(DEFAULT_USER, key.getUser());
+        assertEquals(DEFAULT_RELATION, key.getRelation());
+        assertEquals(DEFAULT_OBJECT, key.getObject());
+    }
+
+    @Test
+    public void read_complexContext() throws Exception {
+        // Given
+        String postUrl = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/read";
+        String expectedBody = String.format(
+                "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"page_size\":null,\"continuation_token\":null}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
+        String responseBody = String.format(
+                "{\"tuples\":[{\"key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"," +
+                        "\"condition\":{\"context\":{" +
+                        "  \"num\":1," +
+                        "  \"str\":\"banana\"," +
+                        "  \"list\":[1, \"banana\", [], {}]," +
+                        "  \"obj\":{" +
+                        "    \"num\":1," +
+                        "    \"str\":\"banana\"," +
+                        "    \"list\":[]," +
+                        "    \"obj\": {}" +
+                        "  }" +
+                        "}}}}]}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
+        mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, responseBody);
+        ReadRequest request = new ReadRequest()
+                .tupleKey(new ReadRequestTupleKey()
                         ._object(DEFAULT_OBJECT)
                         .relation(DEFAULT_RELATION)
                         .user(DEFAULT_USER));
@@ -934,6 +982,19 @@ public class OpenFgaApiTest {
         assertEquals(DEFAULT_USER, key.getUser());
         assertEquals(DEFAULT_RELATION, key.getRelation());
         assertEquals(DEFAULT_OBJECT, key.getObject());
+
+        // The below is subject to change.
+        assertNotNull(key.getCondition());
+        var context = key.getCondition().getContext();
+        assertNotNull(context);
+        var contextMap = assertInstanceOf(Map.class, context);
+        assertEquals(1, contextMap.get("num"));
+        assertEquals("banana", contextMap.get("str"));
+        assertEquals(List.of(1, "banana", List.of(), Map.of()), contextMap.get("list"));
+        assertEquals(Map.of("num", 1,
+                "str", "banana",
+                "list", List.of(),
+                "obj", Map.of()), contextMap.get("obj"));
     }
 
     @Test
@@ -1031,19 +1092,19 @@ public class OpenFgaApiTest {
         // Given
         String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write";
         String expectedBody = String.format(
-                "{\"writes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID);
+                "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID);
         mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY);
         WriteRequest request = new WriteRequest()
                 .authorizationModelId(DEFAULT_AUTH_MODEL_ID)
-                .writes(new TupleKeys()
+                .writes(new WriteRequestWrites()
                         .tupleKeys(List.of(new TupleKey()
                                 ._object(DEFAULT_OBJECT)
                                 .relation(DEFAULT_RELATION)
                                 .user(DEFAULT_USER))));
 
         // When
-        fga.write(DEFAULT_STORE_ID, request);
+        fga.write(DEFAULT_STORE_ID, request).get();
 
         // Then
         mockHttpClient.verify().post(postPath).withBody(is(expectedBody)).called(1);
@@ -1057,19 +1118,112 @@ public class OpenFgaApiTest {
         // Given
         String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write";
         String expectedBody = String.format(
-                "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"authorization_model_id\":\"%s\"}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID);
+                "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}]},\"authorization_model_id\":\"%s\"}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID);
         mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY);
         WriteRequest request = new WriteRequest()
                 .authorizationModelId(DEFAULT_AUTH_MODEL_ID)
-                .deletes(new TupleKeys()
-                        .tupleKeys(List.of(new TupleKey()
+                .deletes(new WriteRequestDeletes()
+                        .tupleKeys(List.of(new TupleKeyWithoutCondition()
                                 ._object(DEFAULT_OBJECT)
                                 .relation(DEFAULT_RELATION)
                                 .user(DEFAULT_USER))));
 
         // When
-        fga.write(DEFAULT_STORE_ID, request);
+        fga.write(DEFAULT_STORE_ID, request).get();
+
+        // Then
+        mockHttpClient.verify().post(postPath).withBody(is(expectedBody)).called(1);
+    }
+
+    @Test
+    public void writeWithContext_map() throws Exception {
+        // Given
+        String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write";
+        String expectedBody = String.format(
+                "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"conditionName\",\"context\":{\"num\":1,\"str\":\"banana\",\"list\":[],\"obj\":{}}}}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID);
+        mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY);
+        var context = new LinkedHashMap<>();
+        context.put("num", 1);
+        context.put("str", "banana");
+        context.put("list", List.of());
+        context.put("obj", new LinkedHashMap<>());
+        WriteRequest request = new WriteRequest()
+                .authorizationModelId(DEFAULT_AUTH_MODEL_ID)
+                .writes(new WriteRequestWrites()
+                        .tupleKeys(List.of(new TupleKey()
+                                ._object(DEFAULT_OBJECT)
+                                .relation(DEFAULT_RELATION)
+                                .user(DEFAULT_USER)
+                                .condition(new RelationshipCondition()
+                                        .name("conditionName")
+                                        .context(context)))));
+
+        // When
+        fga.write(DEFAULT_STORE_ID, request).get();
+
+        // Then
+        mockHttpClient.verify().post(postPath).withBody(is(expectedBody)).called(1);
+    }
+
+    @Test
+    public void writeWithContext_modeledObj() throws Exception {
+        // Given
+
+        String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write";
+        String expectedBody = String.format(
+                "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"conditionName\",\"context\":{\"num\":1,\"str\":\"apple\",\"list\":[2,\"banana\",[],{\"num\":3,\"str\":\"cupcake\",\"list\":null,\"obj\":null}],\"obj\":{\"num\":4,\"str\":\"dolphin\",\"list\":null,\"obj\":null}}}}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID);
+        mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY);
+
+        class TestObj {
+            int num;
+            String str;
+            List<Object> list;
+            Object obj;
+
+            public int getNum() {
+                return num;
+            }
+
+            public String getStr() {
+                return str;
+            }
+
+            public List<Object> getList() {
+                return list;
+            }
+
+            public Object getObj() {
+                return obj;
+            }
+        }
+        var obj = new TestObj();
+        obj.num = 1;
+        obj.str = "apple";
+        var objInList = new TestObj();
+        obj.list = List.of(2, "banana", List.of(), objInList);
+        objInList.num = 3;
+        objInList.str = "cupcake";
+        var objInObj = new TestObj();
+        obj.obj = objInObj;
+        objInObj.num = 4;
+        objInObj.str = "dolphin";
+
+        WriteRequest request = new WriteRequest()
+                .authorizationModelId(DEFAULT_AUTH_MODEL_ID)
+                .writes(new WriteRequestWrites()
+                        .tupleKeys(List.of(new TupleKey()
+                                ._object(DEFAULT_OBJECT)
+                                .relation(DEFAULT_RELATION)
+                                .user(DEFAULT_USER)
+                                .condition(new RelationshipCondition()
+                                        .name("conditionName")
+                                        .context(obj)))));
+
+        // When
+        fga.write(DEFAULT_STORE_ID, request).get();
 
         // Then
         mockHttpClient.verify().post(postPath).withBody(is(expectedBody)).called(1);
@@ -1171,11 +1325,11 @@ public class OpenFgaApiTest {
         // Given
         String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/check";
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":{\"tuple_keys\":[]},\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":{\"tuple_keys\":[]},\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}");
         CheckRequest request = new CheckRequest()
-                .tupleKey(new TupleKey()
+                .tupleKey(new CheckRequestTupleKey()
                         ._object(DEFAULT_OBJECT)
                         .relation(DEFAULT_RELATION)
                         .user(DEFAULT_USER))
@@ -1289,18 +1443,15 @@ public class OpenFgaApiTest {
         // Given
         String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/expand";
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"authorization_model_id\":\"%s\"}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID);
+                "{\"tuple_key\":{\"relation\":\"%s\",\"object\":\"%s\"},\"authorization_model_id\":\"%s\"}",
+                DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID);
         String responseBody = String.format(
                 "{\"tree\":{\"root\":{\"union\":{\"nodes\":[{\"leaf\":{\"users\":{\"users\":[\"%s\"]}}}]}}}}",
                 DEFAULT_USER);
         mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, responseBody);
         ExpandRequest request = new ExpandRequest()
                 .authorizationModelId(DEFAULT_AUTH_MODEL_ID)
-                .tupleKey(new TupleKey()
-                        ._object(DEFAULT_OBJECT)
-                        .relation(DEFAULT_RELATION)
-                        .user(DEFAULT_USER));
+                .tupleKey(new ExpandRequestTupleKey()._object(DEFAULT_OBJECT).relation(DEFAULT_RELATION));
 
         // When
         var response = fga.expand(DEFAULT_STORE_ID, request).get();
@@ -1421,7 +1572,7 @@ public class OpenFgaApiTest {
         // Given
         String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/list-objects";
         String expectedBody = String.format(
-                "{\"authorization_model_id\":\"%s\",\"type\":null,\"relation\":\"%s\",\"user\":\"%s\",\"contextual_tuples\":null}",
+                "{\"authorization_model_id\":\"%s\",\"type\":null,\"relation\":\"%s\",\"user\":\"%s\",\"contextual_tuples\":null,\"context\":null}",
                 DEFAULT_AUTH_MODEL_ID, DEFAULT_RELATION, DEFAULT_USER);
         mockHttpClient
                 .onPost(postPath)
@@ -1537,8 +1688,8 @@ public class OpenFgaApiTest {
         // Given
         String getUrl = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/assertions/01G5JAVJ41T49E9TT3SKVS7X1J";
         String responseBody = String.format(
-                "{\"assertions\":[{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"expectation\":true}]}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"assertions\":[{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"expectation\":true}]}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient.onGet(getUrl).doReturn(200, responseBody);
 
         // When
@@ -1657,12 +1808,12 @@ public class OpenFgaApiTest {
         // Given
         String putUrl = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/assertions/01G5JAVJ41T49E9TT3SKVS7X1J";
         String expectedBody = String.format(
-                "{\"assertions\":[{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"expectation\":true}]}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"assertions\":[{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"expectation\":true}]}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient.onPut(putUrl).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY);
         WriteAssertionsRequest request = new WriteAssertionsRequest()
                 .assertions(List.of(new Assertion()
-                        .tupleKey(new TupleKey()
+                        .tupleKey(new CheckRequestTupleKey()
                                 ._object(DEFAULT_OBJECT)
                                 .relation(DEFAULT_RELATION)
                                 .user(DEFAULT_USER))
diff --git a/config/clients/java/template/README_calling_api.mustache b/config/clients/java/template/README_calling_api.mustache
index 67e6624e..716240ec 100644
--- a/config/clients/java/template/README_calling_api.mustache
+++ b/config/clients/java/template/README_calling_api.mustache
@@ -277,7 +277,7 @@ var request = new ClientWriteRequest()
             ._object("document:budget")
     ))
     .deletes(List.of(
-        new ClientTupleKey()
+        new ClientTupleKeyWithoutCondition()
             .user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
             .relation("writer")
             ._object("document:roadmap")
diff --git a/config/clients/java/template/auth-model.json b/config/clients/java/template/auth-model.json
new file mode 100644
index 00000000..3ef492c5
--- /dev/null
+++ b/config/clients/java/template/auth-model.json
@@ -0,0 +1,67 @@
+{
+    "schema_version": "1.1",
+    "type_definitions": [
+      {
+        "type": "user"
+      },
+      {
+        "type": "document",
+        "relations": {
+          "reader": {
+            "this": {}
+          },
+          "writer": {
+            "this": {}
+          },
+          "owner": {
+            "this": {}
+          }
+        },
+        "metadata": {
+          "relations": {
+            "reader": {
+              "directly_related_user_types": [
+                {
+                  "type": "user"
+                }
+              ]
+            },
+            "writer": {
+              "directly_related_user_types": [
+                {
+                  "type": "user"
+                }
+              ]
+            },
+            "owner": {
+              "directly_related_user_types": [
+                {
+                  "type": "user"
+                }
+              ]
+            },
+            "conditional_reader": {
+              "directly_related_user_types": [
+                {
+                  "condition": "name_starts_with_a",
+                  "type": "user"
+                }
+              ]
+            }
+          }
+        }
+      }
+    ],
+    "conditions": {
+      "name_starts_with_a": {
+        "name": "name_starts_with_a",
+        "expression": "name.startsWith(\"a\")",
+        "parameters": {
+          "name": {
+            "type_name": "TYPE_NAME_STRING"
+          }
+        }
+      }
+    }
+  }
+  
\ No newline at end of file
diff --git a/config/clients/java/template/client-ClientAssertion.java.mustache b/config/clients/java/template/client-ClientAssertion.java.mustache
index 8624fcd2..e3ded689 100644
--- a/config/clients/java/template/client-ClientAssertion.java.mustache
+++ b/config/clients/java/template/client-ClientAssertion.java.mustache
@@ -2,8 +2,7 @@
 package {{invokerPackage}};
 
 import {{modelPackage}}.Assertion;
-import {{modelPackage}}.TupleKey;
-
+import {{modelPackage}}.CheckRequestTupleKey;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -62,7 +61,7 @@ public class ClientAssertion {
     }
 
     public Assertion asAssertion() {
-        TupleKey tupleKey = new TupleKey().user(user).relation(relation)._object(_object);
+        var tupleKey = new CheckRequestTupleKey().user(user).relation(relation)._object(_object);
         return new Assertion().tupleKey(tupleKey).expectation(expectation);
     }
 
diff --git a/config/clients/java/template/client-ClientCheckRequest.java.mustache b/config/clients/java/template/client-ClientCheckRequest.java.mustache
index ff2da5b6..1d477745 100644
--- a/config/clients/java/template/client-ClientCheckRequest.java.mustache
+++ b/config/clients/java/template/client-ClientCheckRequest.java.mustache
@@ -1,6 +1,7 @@
 {{>licenseInfo}}
 package {{invokerPackage}};
 
+import dev.openfga.sdk.api.model.CheckRequestTupleKey;
 import java.util.List;
 
 public class ClientCheckRequest {
@@ -9,6 +10,10 @@ public class ClientCheckRequest {
     private String _object;
     private List<ClientTupleKey> contextualTuples;
 
+    public CheckRequestTupleKey asCheckRequestTupleKey() {
+        return new CheckRequestTupleKey().user(user).relation(relation)._object(_object);
+    }
+
     public ClientCheckRequest _object(String _object) {
         this._object = _object;
         return this;
diff --git a/config/clients/java/template/client-ClientRelationshipCondition.java.mustache b/config/clients/java/template/client-ClientRelationshipCondition.java.mustache
new file mode 100644
index 00000000..bc6ed094
--- /dev/null
+++ b/config/clients/java/template/client-ClientRelationshipCondition.java.mustache
@@ -0,0 +1,31 @@
+{{>licenseInfo}}
+package {{invokerPackage}};
+
+import {{modelPackage}}.RelationshipCondition;
+
+public class ClientRelationshipCondition {
+    private String name;
+    private Object context;
+
+    public ClientRelationshipCondition name(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public ClientRelationshipCondition context(Object context) {
+        this.context = context;
+        return this;
+    }
+
+    public Object getContext() {
+        return context;
+    }
+
+    public RelationshipCondition asRelationshipCondition() {
+        return new RelationshipCondition().name(name).context(context);
+    }
+}
diff --git a/config/clients/java/template/client-ClientTupleKey.java.mustache b/config/clients/java/template/client-ClientTupleKey.java.mustache
index c380b0ed..3b1a826a 100644
--- a/config/clients/java/template/client-ClientTupleKey.java.mustache
+++ b/config/clients/java/template/client-ClientTupleKey.java.mustache
@@ -3,76 +3,63 @@ package {{invokerPackage}};
 
 import {{modelPackage}}.ContextualTupleKeys;
 import {{modelPackage}}.TupleKey;
-import {{modelPackage}}.TupleKeys;
-import java.util.List;
-import java.util.Optional;
+import {{modelPackage}}.WriteRequestWrites;
+import java.util.Collection;
 import java.util.stream.Collectors;
 
-public class ClientTupleKey {
-    private String user;
-    private String relation;
-    private String _object;
+public class ClientTupleKey extends ClientTupleKeyWithoutCondition {
+    private ClientRelationshipCondition condition;
 
-    public ClientTupleKey _object(String _object) {
-        this._object = _object;
+    public ClientTupleKey condition(ClientRelationshipCondition condition) {
+        this.condition = condition;
         return this;
     }
 
-    /**
-     * Get _object
-     * @return _object
-     **/
-    public String getObject() {
-        return _object;
+    public ClientRelationshipCondition getCondition() {
+        return condition;
     }
 
-    public ClientTupleKey relation(String relation) {
-        this.relation = relation;
-        return this;
-    }
+    public TupleKey asTupleKey() {
+        var tupleKey = new TupleKey().user(getUser()).relation(getRelation())._object(getObject());
 
-    /**
-     * Get relation
-     * @return relation
-     **/
-    public String getRelation() {
-        return relation;
-    }
+        if (condition != null) {
+            tupleKey.condition(condition.asRelationshipCondition());
+        }
 
-    public ClientTupleKey user(String user) {
-        this.user = user;
-        return this;
+        return tupleKey;
     }
 
-    /**
-     * Get user
-     * @return user
-     **/
-    public String getUser() {
-        return user;
+    public static ContextualTupleKeys asContextualTupleKeys(Collection<ClientTupleKey> tupleKeys) {
+        return new ContextualTupleKeys()
+                .tupleKeys(tupleKeys.stream()
+                        .map(ClientTupleKey::asTupleKey)
+                        .collect(Collectors.toList()));
     }
 
-    public TupleKey asTupleKey() {
-        return new TupleKey().user(user).relation(relation)._object(_object);
+    public static WriteRequestWrites asWriteRequestWrites(Collection<ClientTupleKey> tupleKeys) {
+        return new WriteRequestWrites()
+                .tupleKeys(tupleKeys.stream()
+                        .map(ClientTupleKey::asTupleKey)
+                        .collect(Collectors.toList()));
     }
 
-    public static Optional<TupleKeys> asTupleKeys(List<ClientTupleKey> clientTupleKeys) {
-        if (clientTupleKeys == null || clientTupleKeys.size() == 0) {
-            return Optional.empty();
-        }
+    /* Overrides for correct typing */
 
-        return Optional.of(new TupleKeys().tupleKeys(asListOfTupleKey(clientTupleKeys)));
+    @Override
+    public ClientTupleKey user(String user) {
+        super.user(user);
+        return this;
     }
 
-    public static ContextualTupleKeys asContextualTupleKeys(List<ClientTupleKey> clientTupleKeys) {
-        if (clientTupleKeys == null || clientTupleKeys.size() == 0) {
-            return new ContextualTupleKeys();
-        }
-
-        return new ContextualTupleKeys().tupleKeys(asListOfTupleKey(clientTupleKeys));
+    @Override
+    public ClientTupleKey relation(String relation) {
+        super.relation(relation);
+        return this;
     }
 
-    private static List<TupleKey> asListOfTupleKey(List<ClientTupleKey> clientTupleKeys) {
-        return clientTupleKeys.stream().map(ClientTupleKey::asTupleKey).collect(Collectors.toList());
+    @Override
+    public ClientTupleKey _object(String _object) {
+        super._object(_object);
+        return this;
     }
 }
diff --git a/config/clients/java/template/client-ClientTupleKeyWithoutCondition.java.mustache b/config/clients/java/template/client-ClientTupleKeyWithoutCondition.java.mustache
new file mode 100644
index 00000000..fc8c597a
--- /dev/null
+++ b/config/clients/java/template/client-ClientTupleKeyWithoutCondition.java.mustache
@@ -0,0 +1,77 @@
+{{>licenseInfo}}
+package {{invokerPackage}};
+
+import {{modelPackage}}.TupleKeyWithoutCondition;
+import {{modelPackage}}.WriteRequestDeletes;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+public class ClientTupleKeyWithoutCondition {
+    private String user;
+    private String relation;
+    private String _object;
+
+    public TupleKeyWithoutCondition asTupleKeyWithoutCondition() {
+        return new TupleKeyWithoutCondition().user(user).relation(relation)._object(_object);
+    }
+
+    public static WriteRequestDeletes asWriteRequestDeletes(Collection<ClientTupleKeyWithoutCondition> tupleKeys) {
+        return new WriteRequestDeletes()
+                .tupleKeys(tupleKeys.stream()
+                        .map(ClientTupleKeyWithoutCondition::asTupleKeyWithoutCondition)
+                        .collect(Collectors.toList()));
+    }
+
+    public ClientTupleKeyWithoutCondition _object(String _object) {
+        this._object = _object;
+        return this;
+    }
+
+    /**
+     * Get _object
+     * @return _object
+     **/
+    public String getObject() {
+        return _object;
+    }
+
+    public ClientTupleKeyWithoutCondition relation(String relation) {
+        this.relation = relation;
+        return this;
+    }
+
+    /**
+     * Get relation
+     * @return relation
+     **/
+    public String getRelation() {
+        return relation;
+    }
+
+    public ClientTupleKeyWithoutCondition user(String user) {
+        this.user = user;
+        return this;
+    }
+
+    /**
+     * Get user
+     * @return user
+     **/
+    public String getUser() {
+        return user;
+    }
+
+    /**
+     * Adds a condition to the tuple key.
+     * @param condition a {@link ClientRelationshipCondition}
+     * @return a new {@link  ClientTupleKey} with this {@link ClientTupleKeyWithoutCondition}'s
+     *         user, relation, and object, and the passed condition.
+     */
+    public ClientTupleKey condition(ClientRelationshipCondition condition) {
+        return new ClientTupleKey()
+                .user(user)
+                .relation(relation)
+                ._object(_object)
+                .condition(condition);
+    }
+}
diff --git a/config/clients/java/template/client-ClientWriteRequest.java.mustache b/config/clients/java/template/client-ClientWriteRequest.java.mustache
index c065d658..a3e0f8b9 100644
--- a/config/clients/java/template/client-ClientWriteRequest.java.mustache
+++ b/config/clients/java/template/client-ClientWriteRequest.java.mustache
@@ -5,7 +5,7 @@ import java.util.List;
 
 public class ClientWriteRequest {
     private List<ClientTupleKey> writes;
-    private List<ClientTupleKey> deletes;
+    private List<ClientTupleKeyWithoutCondition> deletes;
 
     public static ClientWriteRequest ofWrites(List<ClientTupleKey> writes) {
         return new ClientWriteRequest().writes(writes);
@@ -20,16 +20,16 @@ public class ClientWriteRequest {
         return writes;
     }
 
-    public static ClientWriteRequest ofDeletes(List<ClientTupleKey> deletes) {
+    public static ClientWriteRequest ofDeletes(List<ClientTupleKeyWithoutCondition> deletes) {
         return new ClientWriteRequest().deletes(deletes);
     }
 
-    public ClientWriteRequest deletes(List<ClientTupleKey> deletes) {
+    public ClientWriteRequest deletes(List<ClientTupleKeyWithoutCondition> deletes) {
         this.deletes = deletes;
         return this;
     }
 
-    public List<ClientTupleKey> getDeletes() {
+    public List<ClientTupleKeyWithoutCondition> getDeletes() {
         return deletes;
     }
 }
diff --git a/config/clients/java/template/client-HttpRequestAttempt.java.mustache b/config/clients/java/template/client-HttpRequestAttempt.java.mustache
index 64af3963..f2852e24 100644
--- a/config/clients/java/template/client-HttpRequestAttempt.java.mustache
+++ b/config/clients/java/template/client-HttpRequestAttempt.java.mustache
@@ -4,10 +4,13 @@ import static {{utilPackage}}.StringUtil.isNullOrWhitespace;
 import {{configPackage}}.Configuration;
 import {{errorsPackage}}.*;
 import java.io.IOException;
+import java.io.PrintStream;
 import java.net.HttpURLConnection;
 import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.util.Optional;
 import java.util.concurrent.*;
@@ -19,6 +22,9 @@ public class HttpRequestAttempt<T> {
     private final String name;
     private final HttpRequest request;
 
+    // Intended for only testing the OpenFGA SDK itself.
+    private final boolean enableDebugLogging = "enable".equals(System.getProperty("HttpRequestAttempt.debug-logging"));
+
     public HttpRequestAttempt(
             HttpRequest request, String name, Class<T> clazz, ApiClient apiClient, Configuration configuration)
             throws FgaInvalidParameterException {
@@ -33,6 +39,11 @@ public class HttpRequestAttempt<T> {
     }
 
     public CompletableFuture<ApiResponse<T>> attemptHttpRequest() throws ApiException {
+        if (enableDebugLogging) {
+            request.bodyPublisher()
+                    .ifPresent(requestBodyPublisher ->
+                            requestBodyPublisher.subscribe(new BodyLogger(System.err, "request")));
+        }
         int retryNumber = 0;
         return attemptHttpRequest(apiClient.getHttpClient(), retryNumber, null);
     }
@@ -83,4 +94,37 @@ public class HttpRequestAttempt<T> {
                 .executor(CompletableFuture.delayedExecutor(retryDelay.toNanos(), TimeUnit.NANOSECONDS))
                 .build();
     }
+
+    private static class BodyLogger implements Flow.Subscriber<ByteBuffer> {
+        private final PrintStream out;
+        private final String target;
+
+        BodyLogger(PrintStream out, String target) {
+            this.out = out;
+            this.target = target;
+        }
+
+        @Override
+        public void onSubscribe(Flow.Subscription subscription) {
+            out.printf("[%s] subscribed: %s\n", this.getClass().getName(), subscription);
+            subscription.request(Long.MAX_VALUE);
+        }
+
+        @Override
+        public void onNext(ByteBuffer item) {
+            out.printf(
+                    "[%s] %s: %s\n",
+                    this.getClass().getName(), target, new String(item.array(), StandardCharsets.UTF_8));
+        }
+
+        @Override
+        public void onError(Throwable throwable) {
+            out.printf("[%s] error: %s\n", this.getClass().getName(), throwable);
+        }
+
+        @Override
+        public void onComplete() {
+            out.flush();
+        }
+    }
 }
diff --git a/config/clients/java/template/client-OpenFgaClient.java.mustache b/config/clients/java/template/client-OpenFgaClient.java.mustache
index 2c28bf44..067f22b3 100644
--- a/config/clients/java/template/client-OpenFgaClient.java.mustache
+++ b/config/clients/java/template/client-OpenFgaClient.java.mustache
@@ -233,9 +233,10 @@ public class OpenFgaClient {
         ReadRequest body = new ReadRequest();
 
         if (request != null && (request.getUser() != null || request.getRelation() != null || request.getObject() != null)) {
-            TupleKey tupleKey = new TupleKey();
-            tupleKey.user(request.getUser()).relation(request.getRelation())._object(request.getObject());
-            body.tupleKey(tupleKey);
+            body.tupleKey(new ReadRequestTupleKey()
+                    .user(request.getUser())
+                    .relation(request.getRelation())
+                    ._object(request.getObject()));
         }
 
         if (options != null) {
@@ -277,8 +278,15 @@ public class OpenFgaClient {
 
         WriteRequest body = new WriteRequest();
 
-        ClientTupleKey.asTupleKeys(request.getWrites()).ifPresent(body::writes);
-        ClientTupleKey.asTupleKeys(request.getDeletes()).ifPresent(body::deletes);
+        var writeTuples = request.getWrites();
+        if (writeTuples != null && !writeTuples.isEmpty()) {
+            body.writes(ClientTupleKey.asWriteRequestWrites(writeTuples));
+        }
+
+        var deleteTuples = request.getDeletes();
+        if (deleteTuples != null && !deleteTuples.isEmpty()) {
+            body.deletes(ClientTupleKeyWithoutCondition.asWriteRequestDeletes(deleteTuples));
+        }
 
         if (options != null && !isNullOrWhitespace(options.getAuthorizationModelId())) {
             body.authorizationModelId(options.getAuthorizationModelId());
@@ -342,15 +350,16 @@ public class OpenFgaClient {
         configuration.assertValid();
         String storeId = configuration.getStoreIdChecked();
 
-        var request = new WriteRequest();
-        ClientTupleKey.asTupleKeys(tupleKeys).ifPresent(request::writes);
+        var body = new WriteRequest();
+
+        body.writes(ClientTupleKey.asWriteRequestWrites(tupleKeys));
 
         String authorizationModelId = configuration.getAuthorizationModelId();
         if (!isNullOrWhitespace(authorizationModelId)) {
-            request.authorizationModelId(authorizationModelId);
+            body.authorizationModelId(authorizationModelId);
         }
 
-        return call(() -> api.write(storeId, request)).thenApply(ClientWriteResponse::new);
+        return call(() -> api.write(storeId, body)).thenApply(ClientWriteResponse::new);
     }
 
     /**
@@ -358,20 +367,21 @@ public class OpenFgaClient {
      *
      * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
      */
-    public CompletableFuture<ClientWriteResponse> deleteTuples(List<ClientTupleKey> tupleKeys)
+    public CompletableFuture<ClientWriteResponse> deleteTuples(List<ClientTupleKeyWithoutCondition> tupleKeys)
             throws FgaInvalidParameterException {
         configuration.assertValid();
         String storeId = configuration.getStoreIdChecked();
 
-        var request = new WriteRequest();
-        ClientTupleKey.asTupleKeys(tupleKeys).ifPresent(request::deletes);
+        var body = new WriteRequest();
+
+        body.deletes(ClientTupleKeyWithoutCondition.asWriteRequestDeletes(tupleKeys));
 
         String authorizationModelId = configuration.getAuthorizationModelId();
         if (!isNullOrWhitespace(authorizationModelId)) {
-            request.authorizationModelId(authorizationModelId);
+            body.authorizationModelId(authorizationModelId);
         }
 
-        return call(() -> api.write(storeId, request)).thenApply(ClientWriteResponse::new);
+        return call(() -> api.write(storeId, body)).thenApply(ClientWriteResponse::new);
     }
 
     /* **********************
@@ -401,10 +411,7 @@ public class OpenFgaClient {
         CheckRequest body = new CheckRequest();
 
         if (request != null) {
-            body.tupleKey(new TupleKey()
-                    .user(request.getUser())
-                    .relation(request.getRelation())
-                    ._object(request.getObject()));
+            body.tupleKey(request.asCheckRequestTupleKey());
 
             var contextualTuples = request.getContextualTuples();
             if (contextualTuples != null && !contextualTuples.isEmpty()) {
@@ -480,9 +487,8 @@ public class OpenFgaClient {
         ExpandRequest body = new ExpandRequest();
 
         if (request != null) {
-            body.tupleKey(new TupleKey()
-                    .relation(request.getRelation())
-                    ._object(request.getObject()));
+            body.tupleKey(
+                    new ExpandRequestTupleKey().relation(request.getRelation())._object(request.getObject()));
         }
 
         if (options != null && !isNullOrWhitespace(options.getAuthorizationModelId())) {
@@ -518,10 +524,12 @@ public class OpenFgaClient {
         ListObjectsRequest body = new ListObjectsRequest();
 
         if (request != null) {
-            body.user(request.getUser())
-                    .relation(request.getRelation())
-                    .type(request.getType())
-                    .contextualTuples(ClientTupleKey.asContextualTupleKeys(request.getContextualTupleKeys()));
+            body.user(request.getUser()).relation(request.getRelation()).type(request.getType());
+            if (request.getContextualTupleKeys() != null) {
+                var contextualTuples = request.getContextualTupleKeys();
+                var bodyContextualTuples = ClientTupleKey.asContextualTupleKeys(contextualTuples);
+                body.contextualTuples(bodyContextualTuples);
+            }
         }
 
         if (options != null && !isNullOrWhitespace(options.getAuthorizationModelId())) {
diff --git a/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache b/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache
index 4f38673e..b6d3c6b5 100644
--- a/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache
+++ b/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache
@@ -6,31 +6,51 @@ import static org.junit.jupiter.api.Assertions.*;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import {{invokerPackage}}.*;
-import {{modelPackage}}.*;
 import {{configPackage}}.*;
+import {{modelPackage}}.*;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import dev.openfga.errors.ApiException;
 import java.net.http.HttpClient;
 import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
 
+@TestInstance(Lifecycle.PER_CLASS)
 public class OpenFgaClientIntegrationTest {
     private static final ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
-    private static final String DEFAULT_AUTH_MODEL =
-            "{\"schema_version\":\"1.1\",\"type_definitions\":[{\"type\":\"user\"},{\"type\":\"document\",\"relations\":{\"reader\":{\"this\":{}},\"writer\":{\"this\":{}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}]}";
     private static final String DEFAULT_USER = "user:81684243-9356-4421-8fbf-a4f8d36aa31b";
     private static final String DEFAULT_DOC = "document:2021-budget";
-    public static final ClientTupleKey DEFAULT_TUPLE_KEY =
-            new ClientTupleKey().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC);
-    public static final List<ClientTupleKey> DEFAULT_TUPLE_KEYS = List.of(DEFAULT_TUPLE_KEY);
-    public static final ClientAssertion DEFAULT_ASSERTION =
+    private static final ClientTupleKeyWithoutCondition DEFAULT_TUPLE_KEY_NO_CONDITION =
+            new ClientTupleKeyWithoutCondition()
+                    .user(DEFAULT_USER)
+                    .relation("reader")
+                    ._object(DEFAULT_DOC);
+    private static final ClientTupleKey DEFAULT_TUPLE_KEY = new ClientTupleKeyWithoutCondition()
+            .user(DEFAULT_USER)
+            .relation("reader")
+            ._object(DEFAULT_DOC)
+            .condition(null); // TODO: Add integ tests with conditions
+    private static final ClientAssertion DEFAULT_ASSERTION =
             new ClientAssertion().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC).expectation(true);
+    private String authModelJson;
 
     private OpenFgaClient fga;
 
+    @BeforeAll
+    public void loadAuthModelJson() throws IOException {
+        authModelJson = Files.readString(Paths.get("src", "test-integration", "resources", "auth-model.json"));
+    }
+
     @BeforeEach
     public void initializeApi() throws Exception {
+        System.setProperty("HttpRequestAttempt.debug-logging", "enable");
+
         ClientConfiguration apiConfig = new ClientConfiguration().apiUrl("http://localhost:8080");
         fga = new OpenFgaClient(apiConfig);
     }
@@ -121,7 +141,7 @@ public class OpenFgaClientIntegrationTest {
         assertEquals(authModelId, response.getAuthorizationModel().getId());
         String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions());
         assertEquals(
-                "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]}}}}]",
+                "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"conditional_reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"name_starts_with_a\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]}}}}]",
                 typeDefsJson);
     }
 
@@ -149,7 +169,7 @@ public class OpenFgaClientIntegrationTest {
                         String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions());
 
                         assertEquals(
-                                "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]}}}}]",
+                                "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"conditional_reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"name_starts_with_a\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null,\"condition\":\"\"}]}}}}]",
                                 typeDefsJson);
                     } catch (JsonProcessingException ex) {
                         assertNull(ex);
@@ -163,8 +183,7 @@ public class OpenFgaClientIntegrationTest {
         String storeName = thisTestName();
         String storeId = createStore(storeName);
         fga.setStoreId(storeId);
-        WriteAuthorizationModelRequest request =
-                mapper.readValue(DEFAULT_AUTH_MODEL, WriteAuthorizationModelRequest.class);
+        WriteAuthorizationModelRequest request = mapper.readValue(authModelJson, WriteAuthorizationModelRequest.class);
 
         // When
         WriteAuthorizationModelResponse response = fga.writeAuthorizationModel(request).get();
@@ -185,7 +204,8 @@ public class OpenFgaClientIntegrationTest {
         fga.setAuthorizationModelId(authModelId);
 
         ClientWriteRequest writeRequest = new ClientWriteRequest().writes(List.of(DEFAULT_TUPLE_KEY));
-        ClientReadRequest readRequest =  new ClientReadRequest().user(DEFAULT_USER)._object(DEFAULT_DOC);
+        ClientReadRequest readRequest =
+                new ClientReadRequest().user(DEFAULT_USER)._object(DEFAULT_DOC);
 
         // When
         fga.write(writeRequest).get();
@@ -291,7 +311,7 @@ public class OpenFgaClientIntegrationTest {
         // Then
         String responseJson = mapper.writeValueAsString(response.getAssertions());
         assertEquals(
-                "[{\"tuple_key\":{\"object\":\"document:2021-budget\",\"relation\":\"reader\",\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\"},\"expectation\":true}]",
+                "[{\"tuple_key\":{\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\",\"relation\":\"reader\",\"object\":\"document:2021-budget\"},\"expectation\":true}]",
                 responseJson);
     }
 
@@ -313,10 +333,8 @@ public class OpenFgaClientIntegrationTest {
      */
     private String writeAuthModel(String storeId) throws Exception {
         fga.setStoreId(storeId);
-        WriteAuthorizationModelRequest request =
-                mapper.readValue(DEFAULT_AUTH_MODEL, WriteAuthorizationModelRequest.class);
-        WriteAuthorizationModelResponse response =
-                fga.writeAuthorizationModel(request).get();
+        var request = mapper.readValue(authModelJson, WriteAuthorizationModelRequest.class);
+        var response = fga.writeAuthorizationModel(request).get();
         return response.getAuthorizationModelId();
     }
 
diff --git a/config/clients/java/template/client-OpenFgaClientTest.java.mustache b/config/clients/java/template/client-OpenFgaClientTest.java.mustache
index e4ab6614..adbe913f 100644
--- a/config/clients/java/template/client-OpenFgaClientTest.java.mustache
+++ b/config/clients/java/template/client-OpenFgaClientTest.java.mustache
@@ -15,6 +15,7 @@ import {{errorsPackage}}.*;
 import java.net.http.HttpClient;
 import java.time.Duration;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -36,6 +37,8 @@ public class OpenFgaClientTest {
     private static final String DEFAULT_OBJECT = "document:budget";
     private static final String DEFAULT_SCHEMA_VERSION = "1.1";
     private static final String EMPTY_RESPONSE_BODY = "{}";
+    private static final ClientRelationshipCondition DEFAULT_CONDITION =
+            new ClientRelationshipCondition().name("condition").context(Map.of("some", "context"));
     private static final int DEFAULT_MAX_RETRIES = 3;
     private static final Duration DEFAULT_RETRY_DELAY = Duration.ofMillis(100);
 
@@ -46,8 +49,10 @@ public class OpenFgaClientTest {
 
     @BeforeEach
     public void beforeEachTest() throws Exception {
+        System.setProperty("HttpRequestAttempt.debug-logging", "enable");
+
         mockHttpClient = new HttpClientMock();
-        // mockHttpClient.debugOn(); // Uncomment when debugging HTTP requests.
+        mockHttpClient.debugOn();
 
         var mockHttpClientBuilder = mock(HttpClient.Builder.class);
         when(mockHttpClientBuilder.executor(any())).thenReturn(mockHttpClientBuilder);
@@ -658,7 +663,7 @@ public class OpenFgaClientTest {
         // Given
         String postUrl = String.format("https://localhost/stores/%s/authorization-models", DEFAULT_STORE_ID);
         String expectedBody =
-                "{\"type_definitions\":[{\"type\":\"document\",\"relations\":{},\"metadata\":null}],\"schema_version\":\"1.1\"}";
+                "{\"type_definitions\":[{\"type\":\"document\",\"relations\":{},\"metadata\":null}],\"schema_version\":\"1.1\",\"conditions\":{}}";
         String responseBody = String.format("{\"authorization_model_id\":\"%s\"}", DEFAULT_AUTH_MODEL_ID);
         mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(201, responseBody);
         WriteAuthorizationModelRequest request = new WriteAuthorizationModelRequest()
@@ -915,8 +920,8 @@ public class OpenFgaClientTest {
         // Given
         String postUrl = String.format("https://localhost/stores/%s/read", DEFAULT_STORE_ID);
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"page_size\":null,\"continuation_token\":null}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"page_size\":null,\"continuation_token\":null}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         String responseBody = String.format(
                 "{\"tuples\":[{\"key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}}]}",
                 DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
@@ -1041,8 +1046,8 @@ public class OpenFgaClientTest {
         // Given
         String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write";
         String expectedBody = String.format(
-                "{\"writes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID);
+                "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":null}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID);
         mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY);
         ClientWriteRequest request = new ClientWriteRequest()
                 .writes(List.of(new ClientTupleKey()
@@ -1066,11 +1071,11 @@ public class OpenFgaClientTest {
         // Given
         String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write";
         String expectedBody = String.format(
-                "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"authorization_model_id\":\"%s\"}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID);
+                "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}]},\"authorization_model_id\":\"%s\"}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID);
         mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY);
         ClientWriteRequest request = new ClientWriteRequest()
-                .deletes(List.of(new ClientTupleKey()
+                .deletes(List.of(new ClientTupleKeyWithoutCondition()
                         ._object(DEFAULT_OBJECT)
                         .relation(DEFAULT_RELATION)
                         .user(DEFAULT_USER)));
@@ -1086,31 +1091,35 @@ public class OpenFgaClientTest {
     public void writeTest_transactions() throws Exception {
         // Given
         String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write";
-        String tupleBody = String.format(
-                "{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
-        ClientTupleKey tuple = new ClientTupleKey()
+        String writeTupleBody = String.format(
+                "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
+        ClientTupleKeyWithoutCondition tuple = new ClientTupleKeyWithoutCondition()
                 ._object(DEFAULT_OBJECT)
                 .relation(DEFAULT_RELATION)
                 .user(DEFAULT_USER);
+        ClientTupleKey writeTuple = tuple.condition(DEFAULT_CONDITION);
         String write2Body = String.format(
                 "{\"writes\":{\"tuple_keys\":[%s,%s]},\"deletes\":null,\"authorization_model_id\":\"%s\"}",
-                tupleBody, tupleBody, DEFAULT_AUTH_MODEL_ID);
+                writeTupleBody, writeTupleBody, DEFAULT_AUTH_MODEL_ID);
         String write1Body = String.format(
                 "{\"writes\":{\"tuple_keys\":[%s]},\"deletes\":null,\"authorization_model_id\":\"%s\"}",
-                tupleBody, DEFAULT_AUTH_MODEL_ID);
+                writeTupleBody, DEFAULT_AUTH_MODEL_ID);
+        String deleteTupleBody = String.format(
+                "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         String delete2Body = String.format(
                 "{\"writes\":null,\"deletes\":{\"tuple_keys\":[%s,%s]},\"authorization_model_id\":\"%s\"}",
-                tupleBody, tupleBody, DEFAULT_AUTH_MODEL_ID);
+                deleteTupleBody, deleteTupleBody, DEFAULT_AUTH_MODEL_ID);
         String delete1Body = String.format(
                 "{\"writes\":null,\"deletes\":{\"tuple_keys\":[%s]},\"authorization_model_id\":\"%s\"}",
-                tupleBody, DEFAULT_AUTH_MODEL_ID);
+                deleteTupleBody, DEFAULT_AUTH_MODEL_ID);
         mockHttpClient
                 .onPost(postPath)
                 .withBody(isOneOf(write2Body, write1Body, delete2Body, delete1Body))
                 .doReturn(200, EMPTY_RESPONSE_BODY);
         ClientWriteRequest request = new ClientWriteRequest()
-                .writes(List.of(tuple, tuple, tuple, tuple, tuple))
+                .writes(List.of(writeTuple, writeTuple, writeTuple, writeTuple, writeTuple))
                 .deletes(List.of(tuple, tuple, tuple, tuple, tuple));
         ClientWriteOptions options =
                 new ClientWriteOptions().disableTransactions(false).transactionChunkSize(2);
@@ -1134,8 +1143,8 @@ public class OpenFgaClientTest {
         String failedUser = "user:SECOND";
         String skippedUser = "user:third";
         Function<String, String> writeBody = user -> String.format(
-                "{\"writes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, user, DEFAULT_AUTH_MODEL_ID);
+                "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}",
+                user, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID);
         mockHttpClient
                 .onPost(postPath)
                 .withBody(isOneOf(writeBody.apply(firstUser), writeBody.apply(skippedUser)))
@@ -1149,7 +1158,8 @@ public class OpenFgaClientTest {
                         .map(user -> new ClientTupleKey()
                                 ._object(DEFAULT_OBJECT)
                                 .relation(DEFAULT_RELATION)
-                                .user(user))
+                                .user(user)
+                                .condition(DEFAULT_CONDITION))
                         .collect(Collectors.toList()));
         ClientWriteOptions options =
                 new ClientWriteOptions().disableTransactions(false).transactionChunkSize(1);
@@ -1182,20 +1192,27 @@ public class OpenFgaClientTest {
     public void writeTest_nonTransaction() throws Exception {
         // Given
         String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write";
-        String tupleBody = String.format(
-                "{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+        String writeTupleBody = String.format(
+                "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
+        String deleteTupleBody = String.format(
+                "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         String expectedBody = String.format(
                 "{\"writes\":{\"tuple_keys\":[%s,%s,%s]},\"deletes\":{\"tuple_keys\":[%s,%s,%s]},\"authorization_model_id\":\"%s\"}",
-                tupleBody, tupleBody, tupleBody, tupleBody, tupleBody, tupleBody, DEFAULT_AUTH_MODEL_ID);
-        mockHttpClient
-                .onPost(postPath)
-                .withBody(is(expectedBody))
-                .doReturn(200, EMPTY_RESPONSE_BODY);
-        ClientTupleKey tuple = new ClientTupleKey()
+                writeTupleBody,
+                writeTupleBody,
+                writeTupleBody,
+                deleteTupleBody,
+                deleteTupleBody,
+                deleteTupleBody,
+                DEFAULT_AUTH_MODEL_ID);
+        mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY);
+        var tuple = new ClientTupleKey()
                 ._object(DEFAULT_OBJECT)
                 .relation(DEFAULT_RELATION)
-                .user(DEFAULT_USER);
+                .user(DEFAULT_USER)
+                .condition(DEFAULT_CONDITION);
         ClientWriteRequest request = new ClientWriteRequest()
                 .writes(List.of(tuple, tuple, tuple))
                 .deletes(List.of(tuple, tuple, tuple));
@@ -1216,20 +1233,30 @@ public class OpenFgaClientTest {
     public void writeTest_nonTransactionsWithFailure() throws Exception {
         // Given
         String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/write";
-        String tupleBody = String.format(
-                "{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+        String writeTupleBody = String.format(
+                "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
+        String deleteTupleBody = String.format(
+                "{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         String expectedBody = String.format(
                 "{\"writes\":{\"tuple_keys\":[%s,%s,%s]},\"deletes\":{\"tuple_keys\":[%s,%s,%s]},\"authorization_model_id\":\"%s\"}",
-                tupleBody, tupleBody, tupleBody, tupleBody, tupleBody, tupleBody, DEFAULT_AUTH_MODEL_ID);
+                writeTupleBody,
+                writeTupleBody,
+                writeTupleBody,
+                deleteTupleBody,
+                deleteTupleBody,
+                deleteTupleBody,
+                DEFAULT_AUTH_MODEL_ID);
         mockHttpClient
                 .onPost(postPath)
                 .withBody(is(expectedBody))
                 .doReturn(400, "{\"code\":\"validation_error\",\"message\":\"Generic validation error\"}");
-        ClientTupleKey tuple = new ClientTupleKey()
+        var tuple = new ClientTupleKey()
                 ._object(DEFAULT_OBJECT)
                 .relation(DEFAULT_RELATION)
-                .user(DEFAULT_USER);
+                .user(DEFAULT_USER)
+                .condition(DEFAULT_CONDITION);
         ClientWriteRequest request = new ClientWriteRequest()
                 .writes(List.of(tuple, tuple, tuple))
                 .deletes(List.of(tuple, tuple, tuple));
@@ -1252,13 +1279,15 @@ public class OpenFgaClientTest {
         // Given
         String postPath = String.format("https://localhost/stores/%s/write", DEFAULT_STORE_ID);
         String expectedBody = String.format(
-                "{\"writes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"deletes\":null,\"authorization_model_id\":\"%s\"}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID);
+                "{\"writes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}]},"
+                        + "\"deletes\":null,\"authorization_model_id\":\"%s\"}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID);
         mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY);
         List<ClientTupleKey> tuples = List.of(new ClientTupleKey()
                 ._object(DEFAULT_OBJECT)
                 .relation(DEFAULT_RELATION)
-                .user(DEFAULT_USER));
+                .user(DEFAULT_USER)
+                .condition(DEFAULT_CONDITION));
 
         // When
         ClientWriteResponse response = fga.writeTuples(tuples).get();
@@ -1273,10 +1302,10 @@ public class OpenFgaClientTest {
         // Given
         String postPath = String.format("https://localhost/stores/%s/write", DEFAULT_STORE_ID);
         String expectedBody = String.format(
-                "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}]},\"authorization_model_id\":\"%s\"}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_AUTH_MODEL_ID);
+                "{\"writes\":null,\"deletes\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"}]},\"authorization_model_id\":\"%s\"}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID);
         mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY);
-        List<ClientTupleKey> tuples = List.of(new ClientTupleKey()
+        List<ClientTupleKeyWithoutCondition> tuples = List.of(new ClientTupleKeyWithoutCondition()
                 ._object(DEFAULT_OBJECT)
                 .relation(DEFAULT_RELATION)
                 .user(DEFAULT_USER));
@@ -1314,7 +1343,8 @@ public class OpenFgaClientTest {
                 .writes(List.of(new ClientTupleKey()
                         ._object(DEFAULT_OBJECT)
                         .relation(DEFAULT_RELATION)
-                        .user(DEFAULT_USER)));
+                        .user(DEFAULT_USER)
+                        .condition(DEFAULT_CONDITION)));
 
         // When
         ExecutionException execException =
@@ -1340,7 +1370,8 @@ public class OpenFgaClientTest {
                 .writes(List.of(new ClientTupleKey()
                         ._object(DEFAULT_OBJECT)
                         .relation(DEFAULT_RELATION)
-                        .user(DEFAULT_USER)));
+                        .user(DEFAULT_USER)
+                        .condition(DEFAULT_CONDITION)));
 
         // When
         ExecutionException execException =
@@ -1365,7 +1396,8 @@ public class OpenFgaClientTest {
                 .writes(List.of(new ClientTupleKey()
                         ._object(DEFAULT_OBJECT)
                         .relation(DEFAULT_RELATION)
-                        .user(DEFAULT_USER)));
+                        .user(DEFAULT_USER)
+                        .condition(DEFAULT_CONDITION)));
 
         // When
         ExecutionException execException =
@@ -1387,21 +1419,20 @@ public class OpenFgaClientTest {
         // Given
         String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID);
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"}," +
-                        "\"contextual_tuples\":{\"tuple_keys\":[{\"object\":\"%s\",\"relation\":\"owner\",\"user\":\"%s\"}]}," +
-                        "\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER, DEFAULT_OBJECT, DEFAULT_USER);
+                "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},"
+                        + "\"contextual_tuples\":{\"tuple_keys\":[{\"user\":\"%s\",\"relation\":\"owner\",\"object\":\"%s\",\"condition\":{\"name\":\"condition\",\"context\":{\"some\":\"context\"}}}]},"
+                        + "\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_USER, DEFAULT_OBJECT);
         mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}");
         ClientCheckRequest request = new ClientCheckRequest()
                 ._object(DEFAULT_OBJECT)
                 .relation(DEFAULT_RELATION)
                 .user(DEFAULT_USER)
-                .contextualTuples(List.of(
-                        new ClientTupleKey()
-                                ._object(DEFAULT_OBJECT)
-                                .relation("owner")
-                                .user(DEFAULT_USER)
-                ));
+                .contextualTuples(List.of(new ClientTupleKey()
+                        ._object(DEFAULT_OBJECT)
+                        .relation("owner")
+                        .user(DEFAULT_USER)
+                        .condition(DEFAULT_CONDITION)));
         ClientCheckOptions options = new ClientCheckOptions().authorizationModelId(DEFAULT_AUTH_MODEL_ID);
 
         // When
@@ -1498,8 +1529,8 @@ public class OpenFgaClientTest {
         // Given
         String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID);
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}");
         ClientCheckRequest request = new ClientCheckRequest()
                 ._object(DEFAULT_OBJECT)
@@ -1521,8 +1552,8 @@ public class OpenFgaClientTest {
         // Given
         String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID);
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}");
         List<ClientCheckRequest> requests = IntStream.range(0, 20)
                 .mapToObj(ignored -> new ClientCheckRequest()
@@ -1639,8 +1670,8 @@ public class OpenFgaClientTest {
         // Given
         String postPath = "https://localhost/stores/01YCP46JKYM8FJCQ37NMBYHE5X/expand";
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":null},\"authorization_model_id\":\"%s\"}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_AUTH_MODEL_ID);
+                "{\"tuple_key\":{\"relation\":\"%s\",\"object\":\"%s\"},\"authorization_model_id\":\"%s\"}",
+                DEFAULT_RELATION, DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID);
         String responseBody = String.format(
                 "{\"tree\":{\"root\":{\"union\":{\"nodes\":[{\"leaf\":{\"users\":{\"users\":[\"%s\"]}}}]}}}}",
                 DEFAULT_USER);
@@ -1755,7 +1786,7 @@ public class OpenFgaClientTest {
         // Given
         String postPath = String.format("https://localhost/stores/%s/list-objects", DEFAULT_STORE_ID);
         String expectedBody = String.format(
-                "{\"authorization_model_id\":\"%s\",\"type\":null,\"relation\":\"%s\",\"user\":\"%s\",\"contextual_tuples\":{\"tuple_keys\":[]}}",
+                "{\"authorization_model_id\":\"%s\",\"type\":null,\"relation\":\"%s\",\"user\":\"%s\",\"contextual_tuples\":null,\"context\":null}",
                 DEFAULT_AUTH_MODEL_ID, DEFAULT_RELATION, DEFAULT_USER);
         mockHttpClient
                 .onPost(postPath)
@@ -1859,8 +1890,8 @@ public class OpenFgaClientTest {
         // Given
         String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID);
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}");
         ClientListRelationsRequest request = new ClientListRelationsRequest()
                 .relations(List.of(DEFAULT_RELATION))
@@ -1886,8 +1917,8 @@ public class OpenFgaClientTest {
         // Given
         String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID);
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}",
-                DEFAULT_OBJECT, "owner", DEFAULT_USER);
+                "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"%s\",\"trace\":null,\"context\":null}",
+                DEFAULT_USER, "owner", DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID);
         mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":false}");
         ClientListRelationsRequest request = new ClientListRelationsRequest()
                 .relations(List.of("owner"))
@@ -1967,8 +1998,8 @@ public class OpenFgaClientTest {
         // Given
         String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID);
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient
                 .onPost(postUrl)
                 .withBody(is(expectedBody))
@@ -1997,8 +2028,8 @@ public class OpenFgaClientTest {
         // Given
         String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID);
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient
                 .onPost(postUrl)
                 .withBody(is(expectedBody))
@@ -2026,8 +2057,8 @@ public class OpenFgaClientTest {
         // Given
         String postUrl = String.format("https://localhost/stores/%s/check", DEFAULT_STORE_ID);
         String expectedBody = String.format(
-                "{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient
                 .onPost(postUrl)
                 .withBody(is(expectedBody))
@@ -2059,8 +2090,8 @@ public class OpenFgaClientTest {
         String getUrl =
                 String.format("https://localhost/stores/%s/assertions/%s", DEFAULT_STORE_ID, DEFAULT_AUTH_MODEL_ID);
         String responseBody = String.format(
-                "{\"assertions\":[{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"expectation\":true}]}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"assertions\":[{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"expectation\":true}]}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient.onGet(getUrl).doReturn(200, responseBody);
 
         // When
@@ -2180,8 +2211,8 @@ public class OpenFgaClientTest {
         String putUrl =
                 String.format("https://localhost/stores/%s/assertions/%s", DEFAULT_STORE_ID, DEFAULT_AUTH_MODEL_ID);
         String expectedBody = String.format(
-                "{\"assertions\":[{\"tuple_key\":{\"object\":\"%s\",\"relation\":\"%s\",\"user\":\"%s\"},\"expectation\":true}]}",
-                DEFAULT_OBJECT, DEFAULT_RELATION, DEFAULT_USER);
+                "{\"assertions\":[{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"expectation\":true}]}",
+                DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT);
         mockHttpClient.onPut(putUrl).withBody(is(expectedBody)).doReturn(200, EMPTY_RESPONSE_BODY);
         List<ClientAssertion> assertions = List.of(new ClientAssertion()
                 .user(DEFAULT_USER)
diff --git a/config/clients/java/template/creds-OAuth2ClientTest.java.mustache b/config/clients/java/template/creds-OAuth2ClientTest.java.mustache
index 898f35fb..7f4706be 100644
--- a/config/clients/java/template/creds-OAuth2ClientTest.java.mustache
+++ b/config/clients/java/template/creds-OAuth2ClientTest.java.mustache
@@ -29,7 +29,10 @@ class OAuth2ClientTest {
 
     @BeforeEach
     public void setup() throws FgaInvalidParameterException {
+        System.setProperty("HttpRequestAttempt.debug-logging", "enable");
+
         mockHttpClient = new HttpClientMock();
+        mockHttpClient.debugOn();
 
         var credentials = new Credentials(new ClientCredentials()
                 .clientId(CLIENT_ID)