diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9eff8d530..dd5114b11 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -137,6 +137,9 @@
### Bug Fixes
+- **[jdbc-v2]** Fixes issue with `FORMAT` in query unable to override format set by client when used with ClickHouse 26.8+
+ Default format is `RowBinaryWithNamesAndTypes` as before but set on client level and can be set to `null` to rely on
+ query `FORMAT` clause. (https://github.com/ClickHouse/clickhouse-java/issues/3086)
- **[jdbc-v2]** Fixed `DatabaseMetaData#getTables` reporting `TABLE_TYPE = TABLE` for a table with the `BigQuery`
engine (present in `system.table_engines` since ClickHouse `26.8`). The engine was missing from the
engine-to-table-type mapping, so it fell back to the default `TABLE`, and `getTables(..., types = {"REMOTE TABLE"})`
diff --git a/client-v2/src/main/java/com/clickhouse/client/api/Client.java b/client-v2/src/main/java/com/clickhouse/client/api/Client.java
index dbe29a268..bf98524df 100644
--- a/client-v2/src/main/java/com/clickhouse/client/api/Client.java
+++ b/client-v2/src/main/java/com/clickhouse/client/api/Client.java
@@ -59,6 +59,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -90,8 +91,6 @@
import java.util.function.Supplier;
import java.util.stream.Collectors;
-import javax.net.ssl.SSLContext;
-
/**
*
Client is the starting point for all interactions with ClickHouse.
*
@@ -1301,6 +1300,17 @@ public Builder setMetricsRecorder(MetricsRecorder metricsRecorder) {
return this;
}
+ /**
+ * Sets default format used when no format is specified in {@code QuerySettings}.
+ *
+ * @param format - valid ClickHouse format
+ * @return this instance of builder
+ */
+ public Builder queryFormat(String format) {
+ this.setOption(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), ClickHouseFormat.valueOf(format).name());
+ return this;
+ }
+
public Client build() {
// check if endpoint are empty. so can not initiate client
if (this.endpoints.isEmpty()) {
@@ -1908,9 +1918,6 @@ public CompletableFuture query(String sqlQuery, Map parseConfigMap(Map configMap)
default:
parsedValue = config.parseValue(value);
}
- parsedConfig.put(config.getKey(), parsedValue);
+ if (parsedValue != null) {
+ parsedConfig.put(config.getKey(), parsedValue);
+ }
}
}
diff --git a/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java b/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java
index 54d53eec9..8e85f9b8d 100644
--- a/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java
+++ b/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java
@@ -872,10 +872,13 @@ private void logServerErrorResponse(HttpPost req, ClassicHttpResponse httpRespon
private void addHeaders(HttpPost req, Map requestConfig) {
setHeader(req, HttpHeaders.CONTENT_TYPE, CONTENT_TYPE.getMimeType());
if (requestConfig.containsKey(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey())) {
- setHeader(
- req,
- ClickHouseHttpProto.HEADER_FORMAT,
- ((ClickHouseFormat) requestConfig.get(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey())).name());
+ ClickHouseFormat format = (ClickHouseFormat) requestConfig.get(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey());
+ if (format != null) {
+ setHeader(
+ req,
+ ClickHouseHttpProto.HEADER_FORMAT,
+ format.name());
+ }
}
if (requestConfig.containsKey(ClientConfigProperties.QUERY_ID.getKey())) {
setHeader(
diff --git a/client-v2/src/main/java/com/clickhouse/client/api/query/QueryResponse.java b/client-v2/src/main/java/com/clickhouse/client/api/query/QueryResponse.java
index 6c0de2e3e..2858f85ab 100644
--- a/client-v2/src/main/java/com/clickhouse/client/api/query/QueryResponse.java
+++ b/client-v2/src/main/java/com/clickhouse/client/api/query/QueryResponse.java
@@ -82,6 +82,12 @@ public void close() throws Exception {
}
}
+ /**
+ * Returns format of the date stream accessible via {@link #getInputStream()}
+ * This format is set from server response header `X-ClickHouse-Format`.
+ *
+ * @return ClickHouseFormat - format matching server response format.
+ */
public ClickHouseFormat getFormat() {
return format;
}
diff --git a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java
index b50d3700b..a1c9299b9 100644
--- a/client-v2/src/test/java/com/clickhouse/client/ClientTests.java
+++ b/client-v2/src/test/java/com/clickhouse/client/ClientTests.java
@@ -333,7 +333,7 @@ public void testDefaultSettings() {
Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match");
}
}
- Assert.assertEquals(config.size(), 37); // to check everything is set. Increment when new added.
+ Assert.assertEquals(config.size(), 38); // to check everything is set. Increment when new added.
}
try (Client client = new Client.Builder()
@@ -365,9 +365,10 @@ public void testDefaultSettings() {
.setSocketRcvbuf(100000)
.setSocketSndbuf(100000)
.binaryStringSupport(true)
+ .queryFormat(ClickHouseFormat.CSV.name())
.build()) {
Map config = client.getConfiguration();
- Assert.assertEquals(config.size(), 38); // to check everything is set. Increment when new added.
+ Assert.assertEquals(config.size(), 39); // to check everything is set. Increment when new added.
Assert.assertEquals(config.get(ClientConfigProperties.DATABASE.getKey()), "mydb");
Assert.assertEquals(config.get(ClientConfigProperties.MAX_EXECUTION_TIME.getKey()), "10");
Assert.assertEquals(config.get(ClientConfigProperties.COMPRESSION_LZ4_UNCOMPRESSED_BUF_SIZE.getKey()), "300000");
@@ -393,7 +394,7 @@ public void testDefaultSettings() {
Assert.assertEquals(config.get(ClientConfigProperties.SOCKET_SNDBUF_OPT.getKey()), "100000");
Assert.assertEquals(config.get(ClientConfigProperties.SSL_MODE.getKey()), "STRICT");
Assert.assertEquals(config.get(ClientConfigProperties.BINARY_STRING_SUPPORT.getKey()), "true");
-
+ Assert.assertEquals(config.get(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey()), "CSV");
}
}
@@ -437,7 +438,7 @@ public void testWithOldDefaults() {
Assert.assertEquals(config.get(p.getKey()), p.getDefaultValue(), "Default value doesn't match");
}
}
- Assert.assertEquals(config.size(), 37); // to check everything is set. Increment when new added.
+ Assert.assertEquals(config.size(), 38); // to check everything is set. Increment when new added.
}
}
diff --git a/client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java b/client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java
index 1a00d9348..a768f3847 100644
--- a/client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java
+++ b/client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java
@@ -2337,15 +2337,38 @@ public void testEmptyResponse() throws Exception {
@Test(groups = {"integration"})
public void testSettingsNotChanged() throws Exception{
- final QuerySettings settings = Mockito.spy(new QuerySettings());
- try (QueryResponse response = client.query("select 1 FORMAT JSONEachRow", settings).get()) {
+ final QuerySettings settings = Mockito.spy(new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow));
+ try (QueryResponse response = client.query("select 1", settings).get()) {
Mockito.verify(settings, Mockito.times(1)).getAllSettings();
Mockito.verifyNoMoreInteractions(settings);
- Assert.assertNull(settings.getFormat());
+ Assert.assertEquals(settings.getFormat(), ClickHouseFormat.JSONEachRow);
Assert.assertEquals(response.getFormat(), ClickHouseFormat.JSONEachRow);
}
}
+ @Test(groups = {"integration"})
+ public void testFormatSelectionPrecedence() throws Exception {
+ // 1. Explicit QuerySettings format overrides client default
+ QuerySettings settingsFormat = new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow);
+ try (QueryResponse response = client.query("SELECT 1 AS num", settingsFormat).get()) {
+ Assert.assertEquals(response.getFormat(), ClickHouseFormat.JSONEachRow);
+ }
+
+ // 2. Default client format is RowBinaryWithNamesAndTypes
+ try (QueryResponse response = client.query("SELECT 1 AS num").get()) {
+ Assert.assertEquals(response.getFormat(), ClickHouseFormat.RowBinaryWithNamesAndTypes);
+ }
+
+ // 3. Client configured with format set to null allows query SQL FORMAT clause to take effect
+ try (Client nullFormatClient = newClient()
+ .setOption(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), null)
+ .build()) {
+ try (QueryResponse response = nullFormatClient.query("SELECT 1 AS num FORMAT JSONEachRow").get()) {
+ Assert.assertEquals(response.getFormat(), ClickHouseFormat.JSONEachRow);
+ }
+ }
+ }
+
@Test
public void testDuplicateColumnNames() throws Exception {
{
diff --git a/docs/clickhouse-docs/client.mdx b/docs/clickhouse-docs/client.mdx
index 6a2ff98b2..4284bed3f 100644
--- a/docs/clickhouse-docs/client.mdx
+++ b/docs/clickhouse-docs/client.mdx
@@ -585,6 +585,22 @@ This object should be closed as soon as possible to release a connection because
## Query API {#query-api}
+**Format Selection**
+
+Client provides transparent access to the response stream from server. User is free to choose any ClickHouse format in request and read data via `InputStream`
+from `QueryResponse` object.
+Format can be requested via:
+- `QuerySettings#setFormat()` this will set format header in a request.
+- In `FORMAT` clause in query itself.
+- Server settings `default_format` (/reference/settings/session-settings/default#default_format)
+
+There are differences in behavior depending on ClickHouse and Client version:
+- Client < `0.11.0` & ClickHouse < `26.8` - `FORMAT` clause has priority over request format header.
+- Client > `0.11.0` & ClickHouse > `26.8` - request header has priority over `FORMAT` clause.
+ - Client sets header when no format is specified in `QuerySettings` - this doesn't allow to override format in query.
+- Client starting `0.11.0` - sets default `format` value on client level and not on operation level. This allows existig code
+work without changes and new code to use `FORMAT` clause by settings `format` on client to `null`.
+
### query(String sqlQuery) {#querystring-sqlquery}
Sends `sqlQuery` as is. Response format is set by query settings. `QueryResponse` will hold a reference to the response stream that should be consumed by a reader for the supportig format.
diff --git a/docs/features.md b/docs/features.md
index 308f694de..4b780db1e 100644
--- a/docs/features.md
+++ b/docs/features.md
@@ -14,7 +14,7 @@ This document lists stable, user-visible behavior in `client-v2` and `jdbc-v2` t
- Proxy support: Can send requests through configured HTTP proxies, including proxy credentials.
- Connection and socket tuning: Exposes pool sizing, keep-alive, reuse strategy, connect/request/socket timeouts, and low-level socket options.
- Query execution: Executes SQL asynchronously and returns streaming query responses with response metadata and metrics.
-- Query settings: Supports per-query database selection, output format, execution limits, roles, log comments, headers, reusable `Session` objects, session settings, server settings, and network timeout overrides. Settings explicitly set to `null` will not be sent to the server. The output format must be set through the query settings (`QuerySettings#setFormat`) and not with a `FORMAT` clause in the query: the client always sends the format of the settings in the `X-ClickHouse-Format` header, and a `26.8+` server uses that header in preference to a `FORMAT` clause in the query.
+- Query settings: Supports per-query database selection, output format, execution limits, roles, log comments, headers, reusable `Session` objects, session settings, server settings, and network timeout overrides. Settings explicitly set to `null` will not be sent to the server. The default format (`RowBinaryWithNamesAndTypes`) is configured at the client level (`ClientConfigProperties.INPUT_OUTPUT_FORMAT`), sending the `X-ClickHouse-Format` request header. ClickHouse `26.8+` uses that request header in preference to a `FORMAT` clause in the SQL query. To use a `FORMAT` clause in the query string or rely on the server `default_format`, set `format` to `null` (or empty) on the client, connection, or query settings so no format header is sent.
- Parameterized SQL: Accepts named query parameters and can send them through supported HTTP request encodings.
- Result materialization helpers: Provides streaming `Records`, generic row access, and convenience APIs that materialize all rows into generic records or typed POJOs.
- Binary format readers: Reads ClickHouse binary result formats including `Native`, `RowBinary`, `RowBinaryWithNames`, and `RowBinaryWithNamesAndTypes`.
@@ -107,7 +107,7 @@ Compatibility-sensitive traits:
- Binary access to `String`/`FixedString` columns is compatibility-sensitive: `getBytes(...)` and `getBinaryStream(...)` expose the raw column bytes (not a re-encoded text literal), and a `NULL` column returns `null` with `wasNull()` reporting `true`. The `binary_string_support` connection property is passed through to the underlying `client-v2` transport.
- `Geometry` has a stable JDBC mapping: metadata reports SQL type `ARRAY` with type name `Geometry`, read paths return nested Java arrays rather than custom wrappers, and write paths depend on the caller preserving the intended point/array nesting shape.
- JDBC `Geometry` writes share the same ambiguity as the client serializer: variant selection is inferred from nesting depth, so `Ring` versus `LineString` and `Polygon` versus `MultiLineString` are not currently distinguishable when writing through the generic `Geometry` path.
-- JDBC `FORMAT JSONEachRow` support is opt-in through the `jdbc_json_parser_factory` driver property, whose value must be a fully-qualified `JsonParserFactory` class name with a public no-argument constructor; JSONEachRow numeric and structured value behavior follows the selected parser and configured server output settings. Inferred JSON arrays are returned from `ResultSet.getObject(...)` as parser-native `List` values rather than JDBC `Array` values because JSONEachRow does not include element metadata. JDBC temporal typed accessors such as `getTimestamp(...)` are not guaranteed for JSONEachRow result sets; callers that need stable JDBC temporal conversions should use the binary default format or perform application-level conversion from string/object values.
+- JDBC `FORMAT JSONEachRow` support is opt-in through the `jdbc_json_parser_factory` driver property, whose value must be a fully-qualified `JsonParserFactory` class name with a public no-argument constructor; JSONEachRow numeric and structured value behavior follows the selected parser and configured server output settings. Inferred JSON arrays are returned from `ResultSet.getObject(...)` as parser-native `List` values rather than JDBC `Array` values because JSONEachRow does not include element metadata. JDBC temporal typed accessors such as `getTimestamp(...)` are not guaranteed for JSONEachRow result sets; callers that need stable JDBC temporal conversions should use the binary default format or perform application-level conversion from string/object values. To use `FORMAT JSONEachRow` in SQL queries with ClickHouse `26.8+`, configure `format=JSONEachRow` in connection properties or set `format=` (to `null`) so the default binary format request header does not override the SQL `FORMAT` clause.
- Standard `FORMAT JSON` output has ClickHouse-specific `meta` and `data` fields and is not exposed as a JDBC `ResultSet`. JDBC callers that need it should unwrap to `ConnectionImpl`, call `getClient()`, and parse the `QueryResponse` stream directly.
- Binary parameters passed through `setBytes()` are encoded as ClickHouse `unhex(...)` expressions rather than text literals; empty byte arrays map to an empty string expression.
- Stream and reader setters (`setAsciiStream`, `setUnicodeStream`, `setBinaryStream`, `setCharacterStream`, `setNCharacterStream`) are treated as text input encoded with the same string-escaping rules, including length-based truncation when a length is supplied.
diff --git a/docs/integration-client.md b/docs/integration-client.md
index 05df55e08..a6075ec71 100644
--- a/docs/integration-client.md
+++ b/docs/integration-client.md
@@ -492,6 +492,40 @@ Consider these trade-offs:
Always pick the format that minimizes unnecessary transcoding in your application layer.
+### Format Selection
+
+The client provides transparent access to the response stream from ClickHouse. You can request any supported ClickHouse format in your request and read data via the `InputStream` from the `QueryResponse` object.
+
+A response format can be specified in several ways:
+- **`QuerySettings#setFormat(ClickHouseFormat format)`**: Sets the format header (`X-ClickHouse-Format`) for a specific query request.
+- **`FORMAT` clause in SQL**: Appending `FORMAT ` directly in the SQL query string.
+- **Client default setting**: The client sets a default `format` option (`ClientConfigProperties.INPUT_OUTPUT_FORMAT`, defaulting to `RowBinaryWithNamesAndTypes`) at the client level.
+- **Server setting**: ClickHouse server session setting (`default_format`).
+
+**Precedence and Version Differences:**
+
+- **Client < 0.11.0 & ClickHouse < 26.8:** The `FORMAT` clause in the query string takes priority over the request format header.
+- **Client >= 0.11.0 & ClickHouse >= 26.8:** The request format header (`X-ClickHouse-Format`) takes priority over the `FORMAT` clause in the query string.
+- **Client 0.11.0+:** Default `format` is set at the client level rather than at the operation level. This allows existing code to work without changes, while new code can use a SQL `FORMAT` clause by setting `format` on the client or in `QuerySettings` to `null`.
+
+**Inspecting Server Response Format:**
+
+Use `QueryResponse#getFormat()` to inspect the format of the response data stream (resolved from the server `X-ClickHouse-Format` response header):
+
+```java
+import com.clickhouse.client.api.Client;
+import com.clickhouse.client.api.query.QueryResponse;
+import com.clickhouse.client.api.query.QuerySettings;
+import com.clickhouse.data.ClickHouseFormat;
+
+public ClickHouseFormat inspectQueryFormat(Client client, String sql) throws Exception {
+ QuerySettings settings = new QuerySettings().setFormat(ClickHouseFormat.JSONEachRow);
+ try (QueryResponse response = client.query(sql, settings).get()) {
+ return response.getFormat();
+ }
+}
+```
+
## Step 6 — Read operations & tuning
**Goal:** read results efficiently and configure the operation-level settings for heavy analytical reads.
diff --git a/docs/integration-jdbc.md b/docs/integration-jdbc.md
index 4d35fa885..3c28b377e 100644
--- a/docs/integration-jdbc.md
+++ b/docs/integration-jdbc.md
@@ -402,18 +402,67 @@ public boolean checkConnectionHealth(Connection conn, int timeoutSeconds) throws
## Step 4 — Formats under the hood
-**Goal:** understand that JDBC hides format selection, so you can decide up front whether JDBC's fixed contract is sufficient.
+**Goal:** understand how JDBC handles format selection internally and how to configure custom formats like `JSONEachRow`.
-JDBC does **not** expose format selection. The driver picks formats internally by operation type:
+JDBC uses the client's format selection mechanism under the hood. By default, the driver sends `X-ClickHouse-Format: RowBinaryWithNamesAndTypes` for query execution:
| Operation | Internal format | Notes |
|-----------|-----------------|-------|
-| Query (`executeQuery`) | Binary row format from server | Converted to JDBC `ResultSet` rows |
+| Query (`executeQuery`) | `RowBinaryWithNamesAndTypes` | Converted to JDBC `ResultSet` rows |
| Simple INSERT via `Statement` | SQL text | `INSERT INTO t VALUES (...)` |
| `PreparedStatement` INSERT | SQL text or RowBinary | RowBinary when `beta.row_binary_for_simple_insert=true` |
| Writer statement INSERT | RowBinary | Streaming binary writer |
| Batch INSERT | Multi-row SQL rewrite or RowBinary | Depends on statement shape |
+### Format Selection and SQL `FORMAT` Clauses
+
+The response format can be configured using the `format` connection property (`ClientConfigProperties.INPUT_OUTPUT_FORMAT` or `"format"`).
+
+**Important for ClickHouse 26.8+:**
+- On ClickHouse 26.8+, the request format header sent by the driver (`X-ClickHouse-Format`) takes priority over a `FORMAT` clause written in the SQL query string.
+- By default, the driver sends `format=RowBinaryWithNamesAndTypes`.
+- To use a SQL `FORMAT` clause (such as `SELECT ... FORMAT JSONEachRow`) with ClickHouse 26.8+, set `format=JSONEachRow` in connection properties or set `format=` (to `""` empty string) so the default binary format header is omitted and ClickHouse honors the query's `FORMAT` clause.
+
+### Usage of `JSONEachRow` in JDBC
+
+JDBC V2 supports streaming `JSONEachRow` responses as standard `ResultSet` instances. This feature is opt-in and requires configuring a `JsonParserFactory`.
+
+1. **Configure Driver Properties:**
+ Set `jdbc_json_parser_factory` (`DriverProperties.JSON_PARSER_FACTORY`) to the fully-qualified class name of a `JsonParserFactory` implementation (such as `JacksonJsonParserFactory` or `GsonJsonParserFactory`).
+ Set `format` (`ClientConfigProperties.INPUT_OUTPUT_FORMAT`) to `"JSONEachRow"` (or set `format=` when including `FORMAT JSONEachRow` in the query).
+
+```java
+import com.clickhouse.client.api.ClientConfigProperties;
+import com.clickhouse.client.api.data_formats.JacksonJsonParserFactory;
+import com.clickhouse.jdbc.DriverProperties;
+
+public Properties createJsonEachRowProperties() {
+ Properties props = new Properties();
+ props.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), JacksonJsonParserFactory.class.getName());
+ props.setProperty(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), "JSONEachRow");
+ return props;
+}
+```
+
+2. **Execute Query and Process ResultSet:**
+
+```java
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+public void readJsonEachRowResultSet(Connection conn) throws Exception {
+ try (Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT id, name, payload FROM events ORDER BY id")) {
+ while (rs.next()) {
+ int id = rs.getInt("id");
+ String name = rs.getString("name");
+ Object payload = rs.getObject("payload"); // returns parser-native List/Map
+ }
+ }
+}
+```
+
### When JDBC's format contract is not enough
| Goal | JDBC approach | Better alternative |
@@ -421,7 +470,7 @@ JDBC does **not** expose format selection. The driver picks formats internally b
| Simple CRUD / reporting | Standard JDBC — sufficient | — |
| Bulk ingest (millions of rows) | Batch `PreparedStatement` + RowBinary beta | Java Client stream insert |
| Complex type handling | `getObject()` with type map | Java Client POJO/binary readers |
-| Export to a file format | Not supported via JDBC | Java Client with format selection |
+| Export to a file format | `format` property / JSONEachRow | Java Client with format selection |
| BI tool integration | JDBC is the right choice | — |
### Hybrid usage: dropping down to the Java Client
@@ -452,7 +501,7 @@ This hybrid approach allows you to use standard JDBC for simple CRUD and metadat
### Common Pitfalls
-- **No format selection API** — you cannot request `Native`, `Parquet`, or `JSONEachRow` through standard JDBC.
+- **Format selection scope** — format selection can be configured via connection properties (`format=JSONEachRow` or setting `jdbc_json_parser_factory`), but standard JDBC `ResultSet` requires compatible row formats (`RowBinaryWithNamesAndTypes` or `JSONEachRow`). Other wire formats like `Native` or `Parquet` require dropping down to the Java Client.
- **Row-oriented output only** — no column-oriented or parallel block consumption.
- **Type mapping layer** may lose precision or structure for complex types.
- **Text INSERT overhead** — default SQL-based inserts are slower than binary streaming. Use the [Java Client](integration-client.md) for maximum throughput.
diff --git a/docs/releases/0_11_0.md b/docs/releases/0_11_0.md
index 6c40120f9..09ca60ff7 100644
--- a/docs/releases/0_11_0.md
+++ b/docs/releases/0_11_0.md
@@ -11,3 +11,13 @@ was removed, because the kind of the operation is always known where the metrics
Metrics are created by the client, and the constructor takes an internal type
(`com.clickhouse.client.api.internal.ClientStatisticsHolder`), so application code is not expected to call it.
Code that does call it must pass `OperationType.QUERY` or `OperationType.INSERT`.
+
+## CLIENT-V2, JDBC-V2: Format Selection
+
+There are, generally, two ways to set format: in `FORMAT` clause or in request header. The problem is
+when both are set. Before ClickHouse `26.8` `FORMAT` clause has priority and with `26.8` it was changed.
+Now there is a problem with clients relying on old logic. See issue https://github.com/ClickHouse/clickhouse-java/issues/3086
+
+Another problem is that client sets default format when settings missing it. This is fixed in current version
+by setting default format on client level and not on operation.
+
diff --git a/examples/jdbc-v2-json-processors/README.md b/examples/jdbc-v2-json-processors/README.md
index 3eaeb1cb7..1959cfd6d 100644
--- a/examples/jdbc-v2-json-processors/README.md
+++ b/examples/jdbc-v2-json-processors/README.md
@@ -81,13 +81,13 @@ Each read call in `run()` follows the same three-step shape:
2. **Customize if needed** — only inside the subclass, by overriding the
protected hook.
3. **Execute** — `readAll(label, factoryClass)` opens a fresh connection
- with `JSON_PARSER_FACTORY=`, runs the `SELECT ... FORMAT JSONEachRow`
- and iterates the `ResultSet`.
+ with `JSON_PARSER_FACTORY=` and `format=JSONEachRow`, runs the
+ `SELECT ... FORMAT JSONEachRow` and iterates the `ResultSet`.
-Because JDBC selects `JSONEachRow` through SQL text, set the JSON output
-server settings explicitly on the connection when numeric accessors are used:
+Configure the format and JSON output server settings explicitly on the connection:
```java
+props.setProperty(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), "JSONEachRow");
props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_integers"), "0");
props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_floats"), "0");
props.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_decimals"), "0");
diff --git a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java
index 4b54e5c88..25e221f6f 100644
--- a/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java
+++ b/examples/jdbc-v2-json-processors/src/main/java/com/clickhouse/examples/jdbc_v2/json_processors/JdbcV2JsonProcessorsExample.java
@@ -137,6 +137,7 @@ private Properties baseProperties() {
var properties = new Properties();
properties.setProperty("user", user);
properties.setProperty("password", password);
+ properties.setProperty(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), "JSONEachRow");
properties.setProperty(ClientConfigProperties.serverSetting("allow_experimental_json_type"), "1");
properties.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_integers"), "0");
properties.setProperty(ClientConfigProperties.serverSetting("output_format_json_quote_64bit_floats"), "0");
diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java
index d52adc967..677f51add 100644
--- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java
+++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java
@@ -935,7 +935,9 @@ public void testUnwrapping() throws Exception {
@Test(groups = { "integration" })
public void testRawJSONQueryThroughUnderlyingClient() throws Exception {
ObjectMapper mapper = new ObjectMapper();
- try (Connection conn = getJdbcConnection();
+ Properties config = new Properties();
+ config.setProperty(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), "");
+ try (Connection conn = getJdbcConnection(config);
QueryResponse response = conn.unwrap(ConnectionImpl.class).getClient()
.query("SELECT 1 AS x FORMAT JSON")
.get()) {
diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java
index 54f4d0624..1f1629faf 100644
--- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java
+++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java
@@ -297,8 +297,9 @@ public void testJsonEachRowCursorPositionDetectsLastRow() throws SQLException {
public void testJsonEachRowGetObjectReturnsParserNativeArray() throws SQLException {
Properties properties = new Properties();
properties.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), JacksonJsonParserFactory.class.getName());
+ properties.setProperty(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), "JSONEachRow");
try (Connection conn = getJdbcConnection(properties); Statement stmt = conn.createStatement()) {
- try (ResultSet rs = stmt.executeQuery("SELECT [1, 2, 3] AS arr FORMAT JSONEachRow")) {
+ try (ResultSet rs = stmt.executeQuery("SELECT [1, 2, 3] AS arr")) {
Assert.assertTrue(rs.next());
Object value = rs.getObject("arr");
Assert.assertTrue(value instanceof List, "Expected parser-native List but got " + value.getClass());
diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java
index e4a4e5c08..116925ffd 100644
--- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java
+++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/StatementTest.java
@@ -7,6 +7,7 @@
import com.clickhouse.client.api.data_formats.JsonParserFactory;
import com.clickhouse.client.api.internal.ServerSettings;
import com.clickhouse.client.api.query.GenericRecord;
+import com.clickhouse.data.ClickHouseFormat;
import com.clickhouse.data.ClickHouseVersion;
import com.clickhouse.jdbc.internal.SqlParserFacade;
import org.apache.commons.lang3.RandomStringUtils;
@@ -862,8 +863,17 @@ public void testCancelInsertWithSession() throws Exception {
}
@Test(groups = {"integration"})
- public void testTextFormatInResponse() throws Exception {
- try (Connection conn = getJdbcConnection();
+ public void testUnsupportedFormat() throws Exception {
+ Properties config1 = new Properties();
+ config1.setProperty(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), "");
+ try (Connection conn = getJdbcConnection(config1);
+ Statement stmt = conn.createStatement()) {
+ Assert.expectThrows(SQLException.class, () -> stmt.executeQuery("SELECT 1 FORMAT JSON"));
+ }
+
+ Properties config2 = new Properties();
+ config2.setProperty(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), ClickHouseFormat.CSV.name());
+ try (Connection conn = getJdbcConnection(config2);
Statement stmt = conn.createStatement()) {
Assert.expectThrows(SQLException.class, () -> stmt.executeQuery("SELECT 1 FORMAT JSON"));
}
@@ -873,6 +883,24 @@ public void testTextFormatInResponse() throws Exception {
public void testJSONEachRowFormat(Class parserFactory) throws Exception {
Properties properties = new Properties();
properties.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), parserFactory.getName());
+ properties.setProperty(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), "JSONEachRow");
+ try (Connection conn = getJdbcConnection(properties)) {
+ try (Statement stmt = conn.createStatement()) {
+ try (ResultSet rs = stmt.executeQuery("SELECT 1 AS num, 'test' AS str")) {
+ assertTrue(rs.next());
+ assertEquals(rs.getInt("num"), 1);
+ assertEquals(rs.getString("str"), "test");
+ assertFalse(rs.next());
+ }
+ }
+ }
+ }
+
+ @Test(groups = {"integration"}, dataProvider = "testJSONEachRowFormatDP")
+ public void testJSONEachRowFormatWithSqlClause(Class parserFactory) throws Exception {
+ Properties properties = new Properties();
+ properties.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(), parserFactory.getName());
+ properties.setProperty(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), "");
try (Connection conn = getJdbcConnection(properties)) {
try (Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT 1 AS num, 'test' AS str FORMAT JSONEachRow")) {
@@ -887,10 +915,12 @@ public void testJSONEachRowFormat(Class parserFactory) throws
@Test(groups = {"integration"})
public void testJSONEachRowFormatRequiresParserFactory() throws Exception {
- try (Connection conn = getJdbcConnection();
+ Properties properties = new Properties();
+ properties.setProperty(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), "JSONEachRow");
+ try (Connection conn = getJdbcConnection(properties);
Statement stmt = conn.createStatement()) {
try {
- stmt.executeQuery("SELECT 1 AS num FORMAT JSONEachRow");
+ stmt.executeQuery("SELECT 1 AS num");
fail("Expected SQLException");
} catch (SQLException e) {
assertTrue(e.getMessage().contains(DriverProperties.JSON_PARSER_FACTORY.getKey()),