This standalone example shows how to consume FORMAT JSONEachRow responses
through jdbc-v2 with the two factories shipped under
com.clickhouse.client.api.data_formats:
JacksonJsonParserFactoryGsonJsonParserFactory
The driver picks the parser factory by fully-qualified class name from
the jdbc_json_parser_factory driver property.
The value is the FQN of a class that implements JsonParserFactory; the
driver loads it reflectively and instantiates it through a public no-arg
constructor. There is no enum-style selector.
Selection is connection-level: the factory cannot be swapped on an
existing connection. The driver instantiates the named class once during
connection creation and reuses that instance for every JSONEachRow
response served by the connection.
Because the driver instantiates the named class with a no-arg constructor, customization cannot be expressed as constructor arguments. The supported approach is:
- Subclass
JacksonJsonParserFactoryorGsonJsonParserFactoryin your own code. - Override the protected hook:
protected ObjectMapper createMapper()onJacksonJsonParserFactoryβ return any fully-configuredObjectMapper(modules, feature flags, deserializers).protected void customize(GsonBuilder builder)onGsonJsonParserFactoryβ configure theGsonBuilder(number policy,TypeAdapters, date format, ...). The factory still appliessetLenient()on its own afterwards, which is required forJSONEachRow.
- Set
JSON_PARSER_FACTORYto the FQN of the subclass.
This example carries both subclasses as public static final nested classes
inside JdbcV2JsonProcessorsExample (CustomJacksonParserFactory and
CustomGsonParserFactory). The example feeds their FQNs to the driver via
factoryClass.getName(), which for nested classes returns the
Outer$Inner binary form β accepted by Class.forName(...) and by the
driver. If you set JSON_PARSER_FACTORY manually (e.g. from a config file
or JDBC URL) and your subclass is nested, you must use the same $-form;
top-level classes use the ordinary dot-separated FQN.
Both custom subclasses also implement a tiny PayloadConverter interface
defined inside the example: their configured ObjectMapper / Gson is
reused to convert the raw payload value produced by the underlying
library into a typed Payload POJO. Because the JDBC driver only exposes
the factory through the connection (not as a Java object), readAll(...)
detects the interface on the factory class and instantiates its own
converter via the same public no-arg constructor the driver uses. The
default factories do not implement the interface, so readAll(...) logs
the raw map for them β making the contrast between the default behavior
and the customized behavior visible in the output.
JdbcV2JsonProcessorsExample is written as a small component:
JdbcV2JsonProcessorsExample(String url, String user, String password)holds the connection settings and exposes regular instance methods (recreateTable(),loadSampleData(),readAll(label, factoryClass),run()), so the class can be copied as-is into another project and have its individual methods invoked.- Sample rows are kept in a plain
Object[][]constant, separate from the SQL, so the read path stays focused on the parser-factory wiring.
Each read call in run() follows the same three-step shape:
- Pick a factory class β
JacksonJsonParserFactory.class/GsonJsonParserFactory.classfor defaults, or one of the nested custom subclasses. - Customize if needed β only inside the subclass, by overriding the protected hook.
- Execute β
readAll(label, factoryClass)opens a fresh connection withJSON_PARSER_FACTORY=<FQN>, runs theSELECT ... FORMAT JSONEachRowand iterates theResultSet.
Because JDBC selects JSONEachRow through SQL text, set the JSON output
server settings explicitly on the connection when numeric accessors are used:
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");Denormal floating-point values (NaN, Inf, -Inf) are not handled by the
built-in JSON reader yet. Keep output_format_json_quote_denormals=1 and
handle those values as strings if your queries can return them.
ClickHouse 64-bit integers can be larger than the exact integer range of a
JSON floating-point number. Jackson's default map materialization preserves
ordinary integer tokens as integer Number values. Gson's default
Map<String, Object> materialization may surface numbers as floating-point
values, which can round large integers before ResultSet.getLong(...) sees
them.
For Gson, extend GsonJsonParserFactory and configure the object number
strategy:
public final class PreciseGsonJsonParserFactory extends GsonJsonParserFactory {
@Override
protected void customize(GsonBuilder builder) {
builder.setObjectToNumberStrategy(com.google.gson.ToNumberPolicy.LONG_OR_DOUBLE);
}
}Then configure JDBC with the factory class name:
props.setProperty(DriverProperties.JSON_PARSER_FACTORY.getKey(),
PreciseGsonJsonParserFactory.class.getName());The included CustomGsonParserFactory uses this pattern. Use
ToNumberPolicy.BIG_DECIMAL instead when exact decimal representation matters
more than receiving integer tokens as Long.
- JDK 17 or newer
- A running ClickHouse server reachable from the machine running the example
- A locally installed
jdbc-v2snapshot from this repository
From this directory:
mvn compile exec:javaConnection properties can be supplied as system properties:
-DchUrlβ JDBC URL (default:jdbc:clickhouse://localhost:8123/default)-DchUserβ ClickHouse user name (default:default)-DchPasswordβ ClickHouse user password (default: empty)
Example with custom connection properties:
mvn compile exec:java \
-DchUrl=jdbc:clickhouse://localhost:8123/default \
-DchUser=default \
-DchPassword=com.clickhouse.examples.jdbc_v2.json_processors.JdbcV2JsonProcessorsExample
Steps performed by run():
recreateTable()β drops and re-createsjdbc_v2_json_processors_examplewith primitive columns and onepayload JSONcolumn.loadSampleData()β inserts the rows from theSAMPLE_ROWSarray as a single batchedINSERT.readAll(...)is invoked four times, each time pointingJSON_PARSER_FACTORYat a different class:JacksonJsonParserFactoryβ default Jackson;JdbcV2JsonProcessorsExample.CustomJacksonParserFactoryβ nested subclass overridingcreateMapper()to tolerate unknown properties and preserve big integers and decimals exactly, also implementingPayloadConverterto convert each row'spayloadMapinto aPayloadPOJO viaObjectMapper.convertValue(...);GsonJsonParserFactoryβ default Gson;JdbcV2JsonProcessorsExample.CustomGsonParserFactoryβ nested subclass overridingcustomize(GsonBuilder)to use aLONG_OR_DOUBLEnumber policy and disable HTML escaping, also implementingPayloadConverterto convert each row'spayloadMapinto aPayloadPOJO viagson.fromJson(gson.toJsonTree(...)).
Logged rows include the payload value's runtime class so the difference
between the default factories (which surface a LinkedHashMap /
LinkedTreeMap) and the customized factories (which surface a Payload)
shows up directly in the output.
The build keeps both jackson-databind and gson on the classpath so the
example can switch between processors at runtime. Production applications
only need to keep the processor they actually use.