Target client repo
ClickHouse/clickhouse-java
Severity
sev:2 — Visible error (the server returns BAD_QUERY_PARAMETER and the query fails outright, no silent corruption). The affected pattern is Nullable(T) query parameters, which is a common type. There is no flag-flip workaround inside client-v2: the user has to either rewrite the SQL to avoid passing the null, switch to JDBC v2's PreparedStatement (which inlines SQL), or pass the magic sentinel string "\\N" instead of a Java null. None of those are a normal SDK usage path.
Description
The client-v2 Client.query(String sqlQuery, Map<String, Object> queryParams, QuerySettings settings) API forwards parameters to the ClickHouse HTTP endpoint as param_<name>=<value> URL or multipart form fields. The conversion of each value is unconditionally String.valueOf(v) — see clickhouse-java/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java around line 754:
private void addStatementParams(Map<String, Object> requestConfig, BiConsumer<String, String> consumer) {
if (requestConfig.containsKey(KEY_STATEMENT_PARAMS)) {
Map<?, ?> params = (Map<?, ?>) requestConfig.get(KEY_STATEMENT_PARAMS);
params.forEach((k, v) -> consumer.accept("param_" + k, String.valueOf(v)));
}
}
When v is a Java null, String.valueOf((Object)null) returns the literal four-character string "null". That string is sent on the wire as param_x=null. The ClickHouse server then tries to parse "null" as the declared Nullable(T) type and fails with BAD_QUERY_PARAMETER.
The correct wire encoding for a NULL query parameter value is \N. Confirmed against the local server (26.4.2.10):
$ curl -s 'http://localhost:8123/?param_x=null&query=SELECT%20%7Bx%3ANullable(Decimal128(8))%7D'
Code: 457. DB::Exception: Value null cannot be parsed as Nullable(Decimal128(8)) ...
$ curl -s 'http://localhost:8123/?param_x=NULL&query=SELECT%20%7Bx%3ANullable(Decimal128(8))%7D'
Code: 457. DB::Exception: Value NULL cannot be parsed as Nullable(Decimal128(8)) ...
$ curl -s 'http://localhost:8123/?param_x=%5CN&query=SELECT%20%7Bx%3ANullable(Decimal128(8))%7D'
\N
This is the same bug class reported against clickhouse-go (#1760): passing nil / null in the parameter map produces a wire value the server cannot parse against any Nullable(T) type, regardless of T.
Note: this affects client-v2's Client.query(sql, queryParams, settings) API. The legacy jdbc-v2 PreparedStatementImpl.encodeObject path inlines null into the SQL string as the literal NULL and is not affected — it does not use server-side query parameters at all.
ClickHouse server version
Verified against 26.4.2.10 running locally at http://localhost:8123. Code analysis of clickhouse-java main HEAD was also performed; the buggy line is unchanged.
Reproduction
Minimal client-v2 snippet — any null-valued entry in the queryParams map triggers it. The query type is irrelevant; Nullable(Decimal128(8)), Nullable(UUID), Nullable(Bool), etc. all produce equivalent Value null cannot be parsed as Nullable(...) errors.
import com.clickhouse.client.api.Client;
import com.clickhouse.client.api.query.QueryResponse;
import java.util.HashMap;
import java.util.Map;
public class NullParamRepro {
public static void main(String[] args) throws Exception {
try (Client client = new Client.Builder()
.addEndpoint("http://localhost:8123")
.setUsername("default")
.setPassword("")
.build()) {
Map<String, Object> params = new HashMap<>();
params.put("x", null);
// Expected: query returns NULL (or treats {x} as IS NULL in a WHERE clause).
// Actual: ServerException — "Value null cannot be parsed as Nullable(Decimal128(8))"
QueryResponse resp = client
.query("SELECT {x:Nullable(Decimal128(8))}", params, null)
.get();
}
}
}
Expected: the result row contains a SQL NULL.
Actual: BAD_QUERY_PARAMETER error propagated to the caller — Value null cannot be parsed as Nullable(Decimal128(8)) for query parameter 'x' ....
Suggested fix
In clickhouse-java/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java addStatementParams (around line 754–758), special-case null values to send the ClickHouse NULL sentinel rather than the string "null":
params.forEach((k, v) -> consumer.accept("param_" + k, v == null ? "\\N" : String.valueOf(v)));
This matches the server's expected wire encoding for null parameters and resolves the failure for every Nullable(T).
Source bug
ClickHouse/clickhouse-go#1760
Target client repo
ClickHouse/clickhouse-javaSeverity
sev:2— Visible error (the server returnsBAD_QUERY_PARAMETERand the query fails outright, no silent corruption). The affected pattern isNullable(T)query parameters, which is a common type. There is no flag-flip workaround inside client-v2: the user has to either rewrite the SQL to avoid passing the null, switch to JDBC v2'sPreparedStatement(which inlines SQL), or pass the magic sentinel string"\\N"instead of a Javanull. None of those are a normal SDK usage path.Description
The
client-v2Client.query(String sqlQuery, Map<String, Object> queryParams, QuerySettings settings)API forwards parameters to the ClickHouse HTTP endpoint asparam_<name>=<value>URL or multipart form fields. The conversion of each value is unconditionallyString.valueOf(v)— seeclickhouse-java/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.javaaround line 754:When
vis a Javanull,String.valueOf((Object)null)returns the literal four-character string"null". That string is sent on the wire asparam_x=null. The ClickHouse server then tries to parse"null"as the declaredNullable(T)type and fails withBAD_QUERY_PARAMETER.The correct wire encoding for a NULL query parameter value is
\N. Confirmed against the local server (26.4.2.10):This is the same bug class reported against clickhouse-go (#1760): passing
nil/nullin the parameter map produces a wire value the server cannot parse against anyNullable(T)type, regardless ofT.Note: this affects
client-v2'sClient.query(sql, queryParams, settings)API. The legacyjdbc-v2PreparedStatementImpl.encodeObjectpath inlinesnullinto the SQL string as the literalNULLand is not affected — it does not use server-side query parameters at all.ClickHouse server version
Verified against
26.4.2.10running locally athttp://localhost:8123. Code analysis ofclickhouse-javamainHEAD was also performed; the buggy line is unchanged.Reproduction
Minimal client-v2 snippet — any null-valued entry in the
queryParamsmap triggers it. The query type is irrelevant;Nullable(Decimal128(8)),Nullable(UUID),Nullable(Bool), etc. all produce equivalentValue null cannot be parsed as Nullable(...)errors.Expected: the result row contains a SQL
NULL.Actual:
BAD_QUERY_PARAMETERerror propagated to the caller —Value null cannot be parsed as Nullable(Decimal128(8)) for query parameter 'x' ....Suggested fix
In
clickhouse-java/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.javaaddStatementParams(around line 754–758), special-casenullvalues to send the ClickHouse NULL sentinel rather than the string"null":This matches the server's expected wire encoding for null parameters and resolves the failure for every
Nullable(T).Source bug
ClickHouse/clickhouse-go#1760