Skip to content

[client-v2] getTableSchema fails on ClickHouse 26.8: X-ClickHouse-Format header now overrides the query's FORMAT TSKV clause #3068

Description

@Marais

Summary

Client.getTableSchema(...) fails against ClickHouse 26.8 (currently master / the head Docker image) with:

com.clickhouse.client.api.ClientException: Failed to get table schema
 └ com.clickhouse.client.api.ClientException: Failed to parse column `null` defined by type 'null'
    └ java.lang.IllegalArgumentException: Non-null columnName and columnType are required
       at com.clickhouse.data.ClickHouseColumn.of(ClickHouseColumn.java:631)
       at com.clickhouse.client.api.internal.TableSchemaParser.readTSKV(TableSchemaParser.java:31)
       at com.clickhouse.client.api.Client.getTableSchemaImpl(Client.java:1872)
       at com.clickhouse.client.api.Client.getTableSchema(Client.java:1847)

The client contradicts itself: it sends X-ClickHouse-Format: RowBinaryWithNamesAndTypes on every request, but getTableSchemaImpl issues DESCRIBE TABLE <t> FORMAT TSKV and parses the response as TSKV text. Until 26.8 the query's FORMAT clause won and this was harmless. As of 26.8 the header wins, so the server returns RowBinary and the TSKV parser reads binary as text — name and type come back null.

Cause

ClickHouse PR ClickHouse/ClickHouse#105249 ("Accessing tables as files, query construction and out-of-band modification in HTTP interface"), merged 2026-08-17. Commit d2b702e93 states it directly:

X-ClickHouse-Format was an alias for the default_format setting, i.e. a fallback used only when nothing else selected a format. Sending this header means the client definitely wants the data in that format, so it is now an alias for the format setting: an explicit override that wins over the query's FORMAT clause and over the path extension (while output_format still wins over it).

Reproduction

No special client configuration is needed — the header is sent unconditionally.

Client c = new Client.Builder()
    .addEndpoint(Protocol.HTTP, "localhost", 8123, false)
    .setUsername("default").setPassword("test_password")
    .build();
c.getTableSchema("t", "default");   // throws on 26.8, fine on 26.6

Against clickhouse/clickhouse-server:head (26.8.1.1733) this throws; against clickhouse/clickhouse-server:latest (26.6.1.1193) it returns the columns.

The server-side behaviour, isolated with curl — same header, same query, two servers:

$ curl -H "X-ClickHouse-Format: RowBinaryWithNamesAndTypes" ... \
       --data-binary "DESCRIBE TABLE default.t FORMAT TSKV"

26.8.1 (head):    \a 004 n a m e 004 t y p e ...      <- RowBinary
26.6.1 (latest):  name=date\ttype=Date\t...           <- TSKV

And on 26.8 the server's query_log shows the header landing as a real setting on the query:

query:    DESCRIBE TABLE t FORMAT TSKV
Settings: {'database':'default','output_format':'RowBinaryWithNamesAndTypes'}

Reproduced with client-v2 0.9.5. The mechanism is version-independent on the client side — anything that sends the header and relies on a FORMAT clause is affected.

Impact

Broader than getTableSchema:

  • jdbc-v2 calls getTableSchema from ConnectionImpl and PreparedStatementImpl, so every JDBC consumer (BI tools, DBeaver, application code) inherits the failure.
  • Any application query carrying an explicit FORMAT clause through client-v2 now silently receives a different format instead. That half produces no exception — just wrong bytes — which is the more dangerous failure mode.

26.8 has not been released yet, so this is not yet hitting users in the field. It will on the 26.8 GA.

Suggested fix

Stop relying on a FORMAT clause that the client's own header overrides. Either:

  • have getTableSchemaImpl set the format via the same mechanism it already uses for the header (so header and query agree), or
  • omit / override X-ClickHouse-Format for these internal metadata requests, or
  • read the schema in RowBinaryWithNamesAndTypes — the format the header already requests — rather than TSKV.

Worth auditing other internal call sites that embed a FORMAT clause in the SQL text, and documenting the precedence change for users who pass their own FORMAT clauses.

Environment

  • clickhouse-java: client-v2 0.9.5 (mechanism is not specific to this version)
  • ClickHouse: 26.8.1.1733 (head) fails, 26.7.4.58 and 26.6.1.1193 fine
  • Found by the nightly ClickHouse-head matrix of ClickHouse/flink-connector-clickhouse

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions