diff --git a/clickhouse-http-client/pom.xml b/clickhouse-http-client/pom.xml
index 2d2accbf2..a9da2db8a 100644
--- a/clickhouse-http-client/pom.xml
+++ b/clickhouse-http-client/pom.xml
@@ -87,7 +87,7 @@
org.wiremock
wiremock-standalone
- 3.13.0
+ ${wiremock.version}
test
diff --git a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java
index 58e2f76f2..9b435fa24 100644
--- a/clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java
+++ b/clickhouse-http-client/src/main/java/com/clickhouse/client/http/config/ClickHouseHttpOption.java
@@ -116,6 +116,12 @@ public enum ClickHouseHttpOption implements ClickHouseOption {
*/
USE_BASIC_AUTHENTICATION("http_use_basic_auth", true, "Whether to use basic authentication.");
+ /**
+ * Replica tag header used by a proxy to route a request to a specific replica.
+ * ClickHouse Cloud feature only.
+ */
+ public static final String HEADER_REPLICA_TAG = "X-ClickHouse-Replica-Tag";
+
private final String key;
private final Serializable defaultValue;
private final Class extends Serializable> clazz;
diff --git a/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ApacheHttpConnectionImplTest.java b/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ApacheHttpConnectionImplTest.java
index 7a5e99de4..a71cc7ae0 100644
--- a/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ApacheHttpConnectionImplTest.java
+++ b/clickhouse-http-client/src/test/java/com/clickhouse/client/http/ApacheHttpConnectionImplTest.java
@@ -14,6 +14,7 @@
import com.clickhouse.client.http.config.ClickHouseHttpOption;
import com.clickhouse.client.http.config.HttpConnectionProvider;
import com.clickhouse.config.ClickHouseOption;
+import com.clickhouse.data.ClickHouseFormat;
import com.clickhouse.data.ClickHouseUtils;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
@@ -77,6 +78,57 @@ protected Map getClientOptions() {
HttpConnectionProvider.APACHE_HTTP_CLIENT);
}
+ @Test(groups = { "unit" }, dataProvider = "replicaTags")
+ public void testCustomHeadersRouteToReplica(String replicaTag) throws Exception {
+ String host = "replica-router.clickhouse.test";
+ String expectedReplica = "replica-for-requested-tag";
+ String otherReplicaTag = "other-" + replicaTag;
+ WireMockServer mockServer = new WireMockServer(WireMockConfiguration.options().dynamicPort());
+ mockServer.start();
+ try {
+ mockServer.addStubMapping(WireMock.post(WireMock.anyUrl())
+ .withHeader(ClickHouseHttpOption.HEADER_REPLICA_TAG, WireMock.equalTo(replicaTag))
+ .withHeader("Host", WireMock.equalTo(host))
+ .withRequestBody(WireMock.matching("(?is)select\\s+hostname\\(\\).*"))
+ .willReturn(WireMock.ok("hostname()\nString\n" + expectedReplica + "\n"))
+ .build());
+ mockServer.addStubMapping(WireMock.post(WireMock.anyUrl())
+ .withHeader(ClickHouseHttpOption.HEADER_REPLICA_TAG, WireMock.equalTo(otherReplicaTag))
+ .withHeader("Host", WireMock.equalTo(host))
+ .withRequestBody(WireMock.matching("(?is)select\\s+hostname\\(\\).*"))
+ .willReturn(WireMock.ok("hostname()\nString\nother-replica\n"))
+ .build());
+
+ Map options = new HashMap<>();
+ options.put(ClickHouseHttpOption.CONNECTION_PROVIDER, HttpConnectionProvider.APACHE_HTTP_CLIENT);
+ options.put(ClickHouseClientOption.COMPRESS, false);
+ options.put(ClickHouseHttpOption.CUSTOM_HEADERS,
+ ClickHouseHttpOption.HEADER_REPLICA_TAG + "=" + replicaTag + ",Host=" + host);
+
+ try (ClickHouseClient client = ClickHouseClient.builder().config(new ClickHouseConfig(options)).build();
+ ClickHouseResponse response = client.read("http://localhost:" + mockServer.port())
+ .format(ClickHouseFormat.TabSeparatedWithNamesAndTypes)
+ .query("select hostname()").executeAndWait()) {
+ Assert.assertEquals(response.firstRecord().getValue(0).asString(), expectedReplica);
+ }
+
+ mockServer.verify(WireMock.postRequestedFor(WireMock.anyUrl())
+ .withHeader(ClickHouseHttpOption.HEADER_REPLICA_TAG, WireMock.equalTo(replicaTag))
+ .withHeader("Host", WireMock.equalTo(host)));
+ } finally {
+ mockServer.stop();
+ }
+ }
+
+ @DataProvider(name = "replicaTags")
+ public static Object[][] replicaTags() {
+ return new Object[][] {
+ { "550e8400-e29b-41d4-a716-446655440000" },
+ { "replica-primary" },
+ { "replica=primary" }
+ };
+ }
+
@Test(groups = { "integration" })
public void testConnection() throws Exception {
ClickHouseNode server = getServer(ClickHouseProtocol.HTTP);
diff --git a/clickhouse-jdbc/pom.xml b/clickhouse-jdbc/pom.xml
index 68e19d4d5..c4c56727f 100644
--- a/clickhouse-jdbc/pom.xml
+++ b/clickhouse-jdbc/pom.xml
@@ -96,6 +96,12 @@
testng
test
+
+ org.wiremock
+ wiremock-standalone
+ ${wiremock.version}
+ test
+
com.mysql
mysql-connector-j
diff --git a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseConnectionTest.java b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseConnectionTest.java
index 04074f84a..61dc71bf2 100644
--- a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseConnectionTest.java
+++ b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseConnectionTest.java
@@ -13,14 +13,20 @@
import com.clickhouse.client.ClickHouseRequest;
import com.clickhouse.client.ClickHouseServerForTest;
import com.clickhouse.client.config.ClickHouseClientOption;
+import com.clickhouse.client.http.config.ClickHouseHttpOption;
import com.clickhouse.data.ClickHouseCompression;
+import com.clickhouse.data.ClickHouseFormat;
import com.clickhouse.data.ClickHouseUtils;
import com.clickhouse.data.value.UnsignedByte;
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import org.testng.Assert;
import org.testng.SkipException;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class ClickHouseConnectionTest extends JdbcIntegrationTest {
@@ -37,6 +43,62 @@ public ClickHouseConnection newConnection(Properties properties) throws SQLExcep
return (ClickHouseConnection) newDataSource(properties).getConnection();
}
+ @Test(groups = "unit", dataProvider = "replicaTags")
+ public void testCustomHeadersRouteToReplica(String replicaTag) throws Exception {
+ String host = "replica-router.clickhouse.test";
+ String expectedReplica = "replica-for-requested-tag";
+ String otherReplicaTag = "other-" + replicaTag;
+ WireMockServer mockServer = new WireMockServer(WireMockConfiguration.options().dynamicPort());
+ mockServer.start();
+ try {
+ mockServer.addStubMapping(WireMock.post(WireMock.anyUrl())
+ .withHeader(ClickHouseHttpOption.HEADER_REPLICA_TAG, WireMock.equalTo(replicaTag))
+ .withHeader("Host", WireMock.equalTo(host))
+ .withRequestBody(WireMock.matching("(?is)select\\s+hostname\\(\\).*"))
+ .willReturn(WireMock.ok("hostname()\nString\n" + expectedReplica + "\n"))
+ .build());
+ mockServer.addStubMapping(WireMock.post(WireMock.anyUrl())
+ .withHeader(ClickHouseHttpOption.HEADER_REPLICA_TAG, WireMock.equalTo(otherReplicaTag))
+ .withHeader("Host", WireMock.equalTo(host))
+ .withRequestBody(WireMock.matching("(?is)select\\s+hostname\\(\\).*"))
+ .willReturn(WireMock.ok("hostname()\nString\nother-replica\n"))
+ .build());
+
+ Properties properties = new Properties();
+ properties.setProperty(ClickHouseClientOption.SERVER_TIME_ZONE.getKey(), "UTC");
+ properties.setProperty(ClickHouseClientOption.SERVER_VERSION.getKey(), "25.8");
+ properties.setProperty(ClickHouseClientOption.COMPRESS.getKey(), Boolean.FALSE.toString());
+ properties.setProperty(ClickHouseClientOption.FORMAT.getKey(),
+ ClickHouseFormat.TabSeparatedWithNamesAndTypes.name());
+ properties.setProperty(ClickHouseHttpOption.CUSTOM_HEADERS.getKey(),
+ ClickHouseHttpOption.HEADER_REPLICA_TAG + "=" + replicaTag + ",Host=" + host);
+
+ String url = "jdbc:clickhouse:http://localhost:" + mockServer.port() + "?clickhouse.jdbc.v1=true";
+ try (Connection connection = new ClickHouseDataSource(url, properties).getConnection();
+ Statement statement = connection.createStatement();
+ ResultSet resultSet = statement.executeQuery("select hostname()")) {
+ Assert.assertTrue(resultSet.next());
+ Assert.assertEquals(resultSet.getString(1), expectedReplica);
+ Assert.assertFalse(resultSet.next());
+ }
+
+ mockServer.verify(WireMock.postRequestedFor(WireMock.anyUrl())
+ .withHeader(ClickHouseHttpOption.HEADER_REPLICA_TAG, WireMock.equalTo(replicaTag))
+ .withHeader("Host", WireMock.equalTo(host)));
+ } finally {
+ mockServer.stop();
+ }
+ }
+
+ @DataProvider(name = "replicaTags")
+ public static Object[][] replicaTags() {
+ return new Object[][] {
+ { "550e8400-e29b-41d4-a716-446655440000" },
+ { "replica-primary" },
+ { "replica=primary" }
+ };
+ }
+
@Test(groups = "integration")
public void testCentralizedConfiguration() throws SQLException {
if (isCloud()) return; //TODO: testCentralizedConfiguration - Revisit, see: https://github.com/ClickHouse/clickhouse-java/issues/1747
diff --git a/client-v2/pom.xml b/client-v2/pom.xml
index 3836a8c6a..1d9e01327 100644
--- a/client-v2/pom.xml
+++ b/client-v2/pom.xml
@@ -137,7 +137,7 @@
org.wiremock
wiremock-standalone
- 3.13.0
+ ${wiremock.version}
test
diff --git a/client-v2/src/main/java/com/clickhouse/client/api/http/ClickHouseHttpProto.java b/client-v2/src/main/java/com/clickhouse/client/api/http/ClickHouseHttpProto.java
index d2e30f48b..860f74ca6 100644
--- a/client-v2/src/main/java/com/clickhouse/client/api/http/ClickHouseHttpProto.java
+++ b/client-v2/src/main/java/com/clickhouse/client/api/http/ClickHouseHttpProto.java
@@ -52,6 +52,11 @@ public class ClickHouseHttpProto {
public static final String HEADER_SSL_CERT_AUTH = "x-clickhouse-ssl-certificate-auth";
+ /**
+ * Replica tag used by a proxy to route a request to a specific replica.
+ */
+ public static final String HEADER_REPLICA_TAG = "X-ClickHouse-Replica-Tag";
+
/**
* Query parameter to specify the query ID.
*/
diff --git a/docs/clickhouse-docs/client.mdx b/docs/clickhouse-docs/client.mdx
index 6899c68bd..2b5897183 100644
--- a/docs/clickhouse-docs/client.mdx
+++ b/docs/clickhouse-docs/client.mdx
@@ -216,9 +216,9 @@ Configuration is defined during client creation. See `com.clickhouse.client.api.
| Method | Arguments | Description | Default | Key |
|--------|-----------|-------------|---------|-----|
| `setHttpCookiesEnabled(boolean enabled)` | `enabled` - flag to enable/disable | Set if HTTP cookies should be remembered and sent to server back. | - | - |
-| `httpHeader(String key, String value)` | `key` - HTTP header key
`value` - string value | Sets value for a single HTTP header. Previous value is overridden. | `none` | `none` |
-| `httpHeader(String key, Collection values)` | `key` - HTTP header key
`values` - list of string values | Sets values for a single HTTP header. Previous value is overridden. | `none` | `none` |
-| `httpHeaders(Map headers)` | `headers` - map with HTTP headers | Sets multiple HTTP header values at a time. | `none` | `none` |
+| `httpHeader(String key, String value)` | `key` - HTTP header key
`value` - string value | Sets value for a single HTTP header. Previous value is overridden. | `none` | `http_header_*` |
+| `httpHeader(String key, Collection values)` | `key` - HTTP header key
`values` - list of string values | Sets values for a single HTTP header. Previous value is overridden. | `none` | `http_header_*` |
+| `httpHeaders(Map headers)` | `headers` - map with HTTP headers | Sets multiple HTTP header values at a time. | `none` | `http_header_*` |
| `useHttpFormDataForQuery(boolean enable)` | `enable` - flag to enable/disable | Sets whether query parameters should be sent as HTTP form data in the request body instead of the URL. Works only with server-side compression. If client-level compression is enabled, it will be disabled for query requests with parameters, as each parameter is sent as multipart content. | `false` | `client.http.use_form_request_for_query` |
@@ -324,18 +324,79 @@ Server side settings can be set on the client level once while creation (see `se
```
⚠️ When options are set via `setOption` method (either the `Client.Builder` or operation settings class) then server settings name should be prefixed with `clickhouse_setting_`. The `com.clickhouse.client.api.ClientConfigProperties#serverSetting()` may be handy in this case.
-### Custom HTTP Header
+### Custom HTTP Headers {#custom-http-headers}
+
+Custom HTTP headers can be set for all operations (client level) or a single one (operation level). Operation settings override client-level headers with the same name.
+
+**Single header**
-Custom HTTP headers can be set for all operations (client level) or a single one (operation level).
```java showLineNumbers
+Client client = new Client.Builder()
+ .addEndpoint("https://clickhouse-cloud-instance:8443/")
+ .setUsername(user)
+ .setPassword(password)
+ .httpHeader("X-ClickHouse-Quota", "test")
+ .build();
QuerySettings settings = new QuerySettings()
- .httpHeader(HttpHeaders.REFERER, clientReferer)
- .setQueryId(qId);
+ .httpHeader(HttpHeaders.REFERER, clientReferer);
+```
+
+**Multiple headers**
+
+Use repeated `httpHeader` calls, or `httpHeaders(Map)`:
+
+```java showLineNumbers
+Map headers = new HashMap<>();
+headers.put("X-ClickHouse-Quota", "test");
+headers.put("X-ClickHouse-Test", "test");
+
+Client client = new Client.Builder()
+ .addEndpoint("https://clickhouse-cloud-instance:8443/")
+ .setUsername(user)
+ .setPassword(password)
+ .httpHeaders(headers)
+ .build();
+```
+
+**Multiple values for one header**
+
+```java showLineNumbers
+clientBuilder.httpHeader("X-Forwarded-For", Arrays.asList("1.2.3.4", "5.6.7.8"));
+```
+
+When options are set via `setOption` (on `Client.Builder` or an operation settings class), prefix the header name with `http_header_`. `ClientConfigProperties.httpHeader(String)` builds that key:
+
+```java showLineNumbers
+clientBuilder.setOption(ClientConfigProperties.httpHeader("X-ClickHouse-Quota"), "test");
+// equivalent to:
+clientBuilder.setOption("http_header_X-CLICKHOUSE-QUOTA", "test");
+```
+
+## ClickHouse Cloud Features {#clickhouse-cloud-features}
+
+Features that apply when connecting to [ClickHouse Cloud](https://clickhouse.com/cloud).
+
+### Sticky sessions {#sticky-sessions}
+
+ClickHouse Cloud can pin related HTTP requests to the same replica. Send the `X-ClickHouse-Replica-Tag` header so a Cloud proxy routes every request that shares the tag to one replica. Use this for session-scoped objects such as temporary tables, replica-local cache reuse, or read-after-write consistency.
+
+**What to set:** HTTP header `X-ClickHouse-Replica-Tag` (`com.clickhouse.client.api.http.ClickHouseHttpProto.HEADER_REPLICA_TAG`). Use the same tag value for all requests that should stay on one replica.
+```java showLineNumbers
+String replicaTag = "my-app-session-1";
+
+try (Client client = new Client.Builder()
+ .addEndpoint("https://your-service.clickhouse.cloud:8443")
+ .setUsername(user)
+ .setPassword(password)
+ .httpHeader(ClickHouseHttpProto.HEADER_REPLICA_TAG, replicaTag)
+ .build()) {
+ client.query("SELECT hostname()").get();
+}
```
-When options are set via `setOption` method (either the `Client.Builder` or operation settings class) then custom header name should be prefixed with `http_header_`. Method `com.clickhouse.client.api.ClientConfigProperties#httpHeader()` may be handy in this case.
+To configure per operation, set the header on `QuerySettings`, `InsertSettings`, or `CommandSettings` instead of the client builder.
## Common Definitions {#common-definitions}
@@ -524,6 +585,9 @@ Configuration options for insert operations.
| `setInputStreamCopyBufferSize(int size)` | Copy buffer size. The buffer is used during write operations to copy data from user-provided input stream to an output stream. Default: `8196`. |
| `serverSetting(String name, String value)` | Sets individual server settings for an operation. |
| `serverSetting(String name, Collection values)` | Sets individual server settings with multiple values for an operation. Items of the collection should be `String` values. |
+| `httpHeader(String key, String value)` | Sets a custom HTTP header for the insert. |
+| `httpHeader(String key, Collection values)` | Sets a custom HTTP header with multiple values. |
+| `httpHeaders(Map headers)` | Sets multiple custom HTTP headers. |
| `setDBRoles(Collection dbRoles)` | Sets DB roles to be set before executing an operation. Items of the collection should be `String` values. |
| `setOption(String option, Object value)` | Sets a configuration option in raw format. This isn't a server setting. |
@@ -691,6 +755,9 @@ Configuration options for query operations.
| `setUseTimeZone(String timeZone)` | Requests server to use `timeZone` for time conversion. See [session_timezone](/reference/settings/session-settings#session_timezone). |
| `serverSetting(String name, String value)` | Sets individual server settings for an operation. |
| `serverSetting(String name, Collection values)` | Sets individual server settings with multiple values for an operation. Items of the collection should be `String` values. |
+| `httpHeader(String key, String value)` | Sets a custom HTTP header for the query. |
+| `httpHeader(String key, Collection values)` | Sets a custom HTTP header with multiple values. |
+| `httpHeaders(Map headers)` | Sets multiple custom HTTP headers. |
| `setDBRoles(Collection dbRoles)` | Sets DB roles to be set before executing an operation. Items of the collection should be `String` values. |
| `setOption(String option, Object value)` | Sets a configuration option in raw format. This isn't a server setting. |
@@ -1482,21 +1549,49 @@ Java client provides configuration options to set up failover and retry behavior
| retry | `0` | Maximum number of times retry can happen for a request. Zero or a negative value means no retry. Retry sends a request to the same node and only if the ClickHouse server returns the `NETWORK_ERROR` error code |
| repeat_on_session_lock | `true` | Whether to repeat execution when the session is locked until timed out(according to `session_timeout` or `connect_timeout`). The failed request is repeated if the ClickHouse server returns the `SESSION_IS_LOCKED` error code |
-### Adding custom http headers {#v1-adding-custom-http-headers}
+### Adding custom HTTP headers {#v1-adding-custom-http-headers}
-Java client support HTTP/S transport layer in case we want to add custom HTTP headers to the request.
-We should use the custom_http_headers property, and the headers need to be `,` separated. The header key/value should be divided using `=`
+The HTTP transport accepts extra request headers through the `custom_http_headers` property (`ClickHouseHttpOption.CUSTOM_HEADERS`). Each header is `Name=Value`. Separate multiple headers with a comma. Escape a comma inside a value as `\,`. An `=` inside a value is kept as part of the value.
-## Java Client support {#v1-java-client-support}
+**Single header**
+
+```java
+options.put("custom_http_headers", "X-ClickHouse-Quota=test");
+// or
+options.put(ClickHouseHttpOption.CUSTOM_HEADERS, "X-ClickHouse-Quota=test");
+```
+
+**Multiple headers**
```java
options.put("custom_http_headers", "X-ClickHouse-Quota=test, X-ClickHouse-Test=test");
```
-## JDBC Driver {#v1-jdbc-driver}
+You can also set headers on a request:
+
+```java
+client.read(server)
+ .option(ClickHouseHttpOption.CUSTOM_HEADERS, "X-ClickHouse-Quota=test, X-ClickHouse-Test=test")
+ .query("SELECT 1")
+ .executeAndWait();
+```
+
+## ClickHouse Cloud Features {#v1-clickhouse-cloud-features}
+
+Features that apply when connecting to [ClickHouse Cloud](https://clickhouse.com/cloud).
+
+### Sticky sessions {#v1-sticky-sessions}
+
+ClickHouse Cloud can pin related HTTP requests to the same replica. Send the `X-ClickHouse-Replica-Tag` header so a Cloud proxy routes every request that shares the tag to one replica. Use this for session-scoped objects such as temporary tables, replica-local cache reuse, or read-after-write consistency.
+
+**What to set:** HTTP header `X-ClickHouse-Replica-Tag` (`com.clickhouse.client.http.config.ClickHouseHttpOption.HEADER_REPLICA_TAG`) via `custom_http_headers`. Use the same tag value for all requests that should stay on one replica.
```java
-properties.setProperty("custom_http_headers", "X-ClickHouse-Quota=test, X-ClickHouse-Test=test");
+String replicaTag = "my-app-session-1";
+options.put("custom_http_headers", "X-ClickHouse-Replica-Tag=" + replicaTag);
+// or
+options.put(ClickHouseHttpOption.CUSTOM_HEADERS,
+ ClickHouseHttpOption.HEADER_REPLICA_TAG + "=" + replicaTag);
```
diff --git a/docs/clickhouse-docs/jdbc.mdx b/docs/clickhouse-docs/jdbc.mdx
index 66f44e53e..3e6ad5130 100644
--- a/docs/clickhouse-docs/jdbc.mdx
+++ b/docs/clickhouse-docs/jdbc.mdx
@@ -186,6 +186,73 @@ stmt.getLocalSettings().logComment("some-comment");
**Note:** this approach work for single threaded uses of statement because `localSettings` is shared between threads.
+### Custom HTTP Headers {#custom-http-headers}
+
+Custom HTTP headers are passed as client properties. Each header uses the `http_header_` prefix. `ClientConfigProperties.httpHeader(String)` (or `DriverProperties.httpHeader(String)`) builds that key. The V1 `custom_http_headers` property is not used in JDBC V2.
+
+**Single header**
+
+```java showLineNumbers
+Properties properties = new Properties();
+properties.setProperty(ClientConfigProperties.httpHeader("X-ClickHouse-Quota"), "test");
+// equivalent to:
+properties.setProperty("http_header_X-CLICKHOUSE-QUOTA", "test");
+
+Connection conn = Driver.connect("jdbc:ch:https://localhost:8443/", properties);
+```
+
+**Multiple headers**
+
+Set one property per header:
+
+```java showLineNumbers
+Properties properties = new Properties();
+properties.setProperty(ClientConfigProperties.httpHeader("X-ClickHouse-Quota"), "test");
+properties.setProperty(ClientConfigProperties.httpHeader("X-ClickHouse-Test"), "test");
+```
+
+The same keys can be added to the JDBC URL:
+
+```
+jdbc:ch:https://localhost:8443?http_header_X-CLICKHOUSE-QUOTA=test&http_header_X-CLICKHOUSE-TEST=test
+```
+
+To set a header for one statement only, use `StatementImpl#getLocalSettings`:
+
+```java showLineNumbers
+StatementImpl stmt = (StatementImpl) conn.createStatement();
+stmt.getLocalSettings().httpHeader("X-ClickHouse-Quota", "test");
+```
+
+## ClickHouse Cloud Features {#clickhouse-cloud-features}
+
+Features that apply when connecting to [ClickHouse Cloud](https://clickhouse.com/cloud).
+
+### Sticky sessions {#sticky-sessions}
+
+ClickHouse Cloud can pin related HTTP requests to the same replica. Send the `X-ClickHouse-Replica-Tag` header so a Cloud proxy routes every request that shares the tag to one replica. Use this for session-scoped objects such as temporary tables, replica-local cache reuse, or read-after-write consistency.
+
+**What to set:** HTTP header `X-ClickHouse-Replica-Tag` (`com.clickhouse.client.api.http.ClickHouseHttpProto.HEADER_REPLICA_TAG`). Use the same tag value for all requests that should stay on one replica.
+
+```java showLineNumbers
+String replicaTag = "my-app-session-1";
+
+Properties properties = new Properties();
+properties.setProperty("user", user);
+properties.setProperty("password", password);
+properties.setProperty(
+ ClientConfigProperties.httpHeader(ClickHouseHttpProto.HEADER_REPLICA_TAG),
+ replicaTag);
+
+Connection conn = Driver.connect("jdbc:ch:https://your-service.clickhouse.cloud:8443", properties);
+```
+
+URL equivalent:
+
+```
+jdbc:ch:https://your-service.clickhouse.cloud:8443?http_header_X-CLICKHOUSE-REPLICA-TAG=my-app-session-1
+```
+
## Supported data types {#supported-data-types}
JDBC driver supports the same data formats as the underlying [java client](/integrations/language-clients/java/index#supported-data-types).
@@ -762,6 +829,7 @@ properties.setProperty("socket_keepalive", "true");
- Streaming Data isn't supported in JDBC V2 because it isn't part of the JDBC spec and Java.
- JDBC V2 expects explicit configuration. No failover defaults.
- Protocol should be specified in the URL. No implicit protocol detection using port numbers.
+- Custom HTTP headers use one property per header (`http_header_*`). The V1 comma-separated `custom_http_headers` property is not accepted.
### Configuration Changes {#configuration-changes}
@@ -1020,6 +1088,51 @@ Since version `0.5.0`, we're using Apache HTTP Client that's packed the Client.
Note: please refer to [JDBC specific configuration](https://github.com/ClickHouse/clickhouse-java/blob/main/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcConfig.java) for more.
+### Custom HTTP Headers {#v07-custom-http-headers}
+
+Extra request headers are set with `custom_http_headers`. Each header is `Name=Value`. Separate multiple headers with a comma. Escape a comma inside a value as `\,`. An `=` inside a value is kept as part of the value.
+
+**Single header**
+
+```java
+properties.setProperty("custom_http_headers", "X-ClickHouse-Quota=test");
+```
+
+**Multiple headers**
+
+```java
+properties.setProperty("custom_http_headers", "X-ClickHouse-Quota=test, X-ClickHouse-Test=test");
+```
+
+The same property can be added to the JDBC URL. URL-encode `=` in the value as `%3D`:
+
+```
+jdbc:ch:http://localhost:8123?custom_http_headers=X-ClickHouse-Quota%3Dtest,X-ClickHouse-Test%3Dtest
+```
+
+## ClickHouse Cloud Features {#v07-clickhouse-cloud-features}
+
+Features that apply when connecting to [ClickHouse Cloud](https://clickhouse.com/cloud).
+
+### Sticky sessions {#v07-sticky-sessions}
+
+ClickHouse Cloud can pin related HTTP requests to the same replica. Send the `X-ClickHouse-Replica-Tag` header so a Cloud proxy routes every request that shares the tag to one replica. Use this for session-scoped objects such as temporary tables, replica-local cache reuse, or read-after-write consistency.
+
+**What to set:** HTTP header `X-ClickHouse-Replica-Tag` (`com.clickhouse.client.http.config.ClickHouseHttpOption.HEADER_REPLICA_TAG`) via `custom_http_headers`. Use the same tag value for all requests that should stay on one replica.
+
+```java
+String replicaTag = "my-app-session-1";
+
+Properties properties = new Properties();
+properties.setProperty("custom_http_headers", "X-ClickHouse-Replica-Tag=" + replicaTag);
+
+ClickHouseDataSource dataSource = new ClickHouseDataSource(
+ "jdbc:clickhouse:https://your-service.clickhouse.cloud:8443", properties);
+try (Connection conn = dataSource.getConnection("default", "password")) {
+ // requests on this connection are pinned to the replica selected for replicaTag
+}
+```
+
## Supported data types {#v07-supported-data-types}
JDBC driver supports same data formats as client library does.
diff --git a/jdbc-v2/pom.xml b/jdbc-v2/pom.xml
index 3f0989dee..be33c5512 100644
--- a/jdbc-v2/pom.xml
+++ b/jdbc-v2/pom.xml
@@ -105,7 +105,7 @@
org.wiremock
wiremock-standalone
- 3.13.0
+ ${wiremock.version}
test
diff --git a/pom.xml b/pom.xml
index 7e402d341..5ba91b64b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -106,6 +106,7 @@
1.21.3
7.5.1
+ 3.13.0
3.1.4
8.1.0