All Products
Search
Document Center

ApsaraDB for ClickHouse:Connect using JDBC

Last Updated:Mar 09, 2026

This topic describes how to use Java Database Connectivity (JDBC) in a Maven project to connect to an ApsaraDB for ClickHouse cluster.

Prerequisites

Procedure

The following steps describe how to connect to an ApsaraDB for ClickHouse cluster using JDBC in a new or existing Maven project. You can also download the complete project sample.

Step 1: Create a Maven project

If you have an existing Maven project, skip this step.

Use Eclipse or another Integrated Development Environment (IDE) to create a Maven project.

Step 2: Import the ClickHouse driver dependency package

Add the following configuration to the pom.xml file.

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>3.4.5</version>
</dependency>
<dependency>
    <groupId>com.clickhouse</groupId>
    <artifactId>clickhouse-jdbc</artifactId>
    <version>0.4.6</version>
</dependency>
<dependency>
    <groupId>org.lz4</groupId>
    <artifactId>lz4-java</artifactId>
    <version>1.8.0</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.2.1</version>
</dependency>

Step 3: Write the application code

Code flow

The following are the main steps to connect to and use an ApsaraDB for ClickHouse cluster with JDBC.

image

The parameters include cluster information and other connection settings. The following table describes the parameters.

Parameter

Description

Example

YOUR_INSTANCE_PROTOCOL

The connection protocol. The value is fixed to "http".

http

YOUR_INSTANCE_ENDPOINT

The endpoint.

Format: VPC_ENDPOINT:8123. `VPC_ENDPOINT` is the VPC endpoint or public endpoint of the cluster.

cc-bp128o64g****ky35-clickhouse.clickhouseserver.rds.aliyuncs.com:8123

DATABASE

The database to connect to.

testDB

YOUR_INSTANCE_USER

The database account.

test

YOUR_INSTANCE_PASSWORD

The password of the database account.

Password****

INSERT_BATCH_SIZE

The number of data rows to insert in a batch. Unit: rows.

10000

INSERT_BATCH_NUM

The number of batches to be inserted by each thread. Unit: batches.

10

ENTERPRISE

The table engine to use. The table engine varies based on the cluster edition.

  • `true`: Enterprise Edition cluster.

  • `false`: Community Edition cluster.

true

INSERT_OPTIMIZE_LEVEL

The optimization level for insert performance. Valid values: 1, 2, and 3.

The insert speed ranks as follows: 3 > 2 > 1.

3

Complete sample code

The following sample code shows how to create a table named test in the default database of an Enterprise Edition cluster and concurrently insert 10 batches of data, with 10,000 rows per batch.

Before you run the code, modify the parameters as needed for your business scenario. For more information about the parameters, see the parameter table in the Code flow section.

The main logic and entry point of this code is the main method.

package com.aliyun;

import com.clickhouse.jdbc.ClickHouseDataSource;
import com.clickhouse.data.ClickHouseOutputStream;
import com.clickhouse.data.ClickHouseWriter;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    private final static String YOUR_INSTANCE_PROTOCOL = "http";
    private final static String YOUR_INSTANCE_ENDPOINT = "VPC_ENDPOINT:8123"; // YOUR CONFIG HERE
    private final static String DATABASE = "default"; // YOUR CONFIG HERE
    private final static String YOUR_INSTANCE_USER = "USER"; // YOUR CONFIG HERE
    private final static String YOUR_INSTANCE_PASSWORD = "PASSWORD"; // YOUR CONFIG HERE
    private final static String JDBC_URL = "jdbc:clickhouse:%s://%s/%s";
    private final static Integer INSERT_BATCH_SIZE = 10000;
    private final static Integer INSERT_BATCH_NUM = 10;
    private final static boolean ENTERPRISE = true; // YOUR CONFIG HERE
    private final static Integer INSERT_OPTIMIZE_LEVEL = 3;

    public static void main(String[] args) {
        try {
            HikariConfig conf = buildHikariDataSource();
            try(HikariDataSource ds = new HikariDataSource(conf)) {
                // Create a table.
                Connection conn = ds.getConnection();
                createTable(conn);
                conn.close();

                // Concurrently insert data.
                int concurrentNum = 5;
                CountDownLatch countDownLatch = new CountDownLatch(concurrentNum);
                ExecutorService executorService = Executors.newFixedThreadPool(concurrentNum);
                for (int i = 0; i < concurrentNum; i++) {
                    executorService.submit(() -> {
                        System.out.printf("[%d] Thread starts inserting\n", Thread.currentThread().getId());
                        try(Connection connection = ds.getConnection()) {
                            batchInsert(connection, INSERT_OPTIMIZE_LEVEL);
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            System.out.printf("[%d] Thread stops inserting\n", Thread.currentThread().getId());
                            countDownLatch.countDown();
                        }
                    });
                }
                // Wait for all threads to finish.
                countDownLatch.await();

                // Count the table.
                conn = ds.getConnection();
                count(conn);
                conn.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Generate the JDBC URL.
     * @param protocol The protocol. Supported protocols include http, https, and grpc.
     * @param endpoint The endpoint.
     * @return The JDBC URL.
     */
    public static String getJdbcUrl(String protocol, String endpoint, String database) {
        return String.format(JDBC_URL, protocol, endpoint, database);
    }

    /**
     * Build HikariDataSource.
     * @return The HikariConfig.
     */
    public static HikariConfig buildHikariDataSource() throws Exception {
        HikariConfig conf = new HikariConfig();

        // Properties
        Properties properties = new Properties();
        /// Socket keepalive
        properties.setProperty("socket_keepalive", "true");
        properties.setProperty("http_connection_provider", "APACHE_HTTP_CLIENT");
        /// Socket timeout
        properties.setProperty("socket_timeout", "120000");
        /// Timezone
        properties.setProperty("use_server_time_zone", "true");

        // Data source configuration
        conf.setDataSource(new ClickHouseDataSource(getJdbcUrl(YOUR_INSTANCE_PROTOCOL, YOUR_INSTANCE_ENDPOINT, DATABASE), properties));
        conf.setUsername(YOUR_INSTANCE_USER);
        conf.setPassword(YOUR_INSTANCE_PASSWORD);

        // Connection pool configuration
        conf.setMaximumPoolSize(10);
        conf.setMinimumIdle(5);
        conf.setIdleTimeout(30000);
        conf.setMaxLifetime(60000);
        conf.setConnectionTimeout(30000);
        conf.setPoolName("HikariPool");

        return conf;
    }

    /**
     * Create a table.
     * @param conn The ClickHouse connection.
     * @throws Exception
     */
    public static void createTable(Connection conn) throws Exception {
        try(Statement statement = conn.createStatement()) {
            if (ENTERPRISE) {
                statement.execute("CREATE TABLE IF NOT EXISTS `default`.`test` ON CLUSTER default (id Int64, name String) ENGINE = MergeTree() ORDER BY id;");
            } else {
                // Create a local table.
                statement.execute("CREATE TABLE IF NOT EXISTS `default`.`test_local` ON CLUSTER default (id Int64, name String) ENGINE = MergeTree() ORDER BY id;");
                // Create a distributed table.
                statement.execute("CREATE TABLE IF NOT EXISTS `default`.`test` ON CLUSTER default (id Int64, name String) ENGINE = Distributed(default, default, test_local, rand());");
            }
        }
    }

    /**
     * Insert data in batches.
     * @param conn The ClickHouse connection.
     * @param optimizeLevel The insert optimization level. 3 is faster than 2, and 2 is faster than 1.<br/>
     *                      1: insert into `default`.`test` (id, name) values(?, ?) -- with an additional query to get the table structure.
     *                         This is portable.<br/>
     *                      2: insert into `default`.`test` select id, name from input('id Int64, name String') -- effectively converts and inserts data sent to the server
     *                         with a given structure into the table with another structure. This is NOT portable because it is limited to ClickHouse.<br/>
     *                      3: insert into `default`.`test` format RowBinary -- fastest (close to the Java client) with streaming mode but requires manual serialization.
     *                         This is NOT portable because it is limited to ClickHouse.
     * @throws Exception
     */
    public static void batchInsert(Connection conn, int optimizeLevel) throws Exception {
        PreparedStatement preparedStatement = null;
        try {
            // Prepared statement
            switch (optimizeLevel) {
                case 1:
                    preparedStatement = conn.prepareStatement("insert into `default`.`test` (id, name) values(?, ?)");
                    break;
                case 2:
                    preparedStatement = conn.prepareStatement("insert into `default`.`test` select id, name from input('id Int64, name String')");
                    break;
                case 3:
                    preparedStatement = conn.prepareStatement("insert into `default`.`test` format RowBinary");
                    break;
                default:
                    throw new IllegalArgumentException("optimizeLevel must be 1, 2 or 3");
            }

            // Insert data.
            long randBase = (long) (Math.random() * 1000000); // A random number to prevent data duplication and loss.
            for (int i = 0; i < INSERT_BATCH_NUM; i++) {
                long insertStartTime = System.currentTimeMillis();
                switch (optimizeLevel) {
                    case 1:
                    case 2:
                        for (int j = 0; j < INSERT_BATCH_SIZE; j++) {
                            long id = (long) i * INSERT_BATCH_SIZE + j + randBase;
                            preparedStatement.setLong(1, id);
                            preparedStatement.setString(2, "name" + id);
                            preparedStatement.addBatch();
                        }
                        preparedStatement.executeBatch();
                        break;
                    case 3:
                        class MyClickHouseWriter implements ClickHouseWriter {
                            int batchIndex = 0;
                            public MyClickHouseWriter(int batchIndex) {
                                this.batchIndex = batchIndex;
                            }
                            @Override
                            public void write(ClickHouseOutputStream clickHouseOutputStream) throws IOException {
                                for (int j = 0; j < INSERT_BATCH_SIZE; j++) {
                                    long id = (long) batchIndex * INSERT_BATCH_SIZE + j + randBase;
                                    // Write id (Int64).
                                    ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
                                    buffer.order(ByteOrder.LITTLE_ENDIAN);
                                    buffer.putLong(id);
                                    clickHouseOutputStream.write(buffer.array());
                                    // Write name (String).
                                    clickHouseOutputStream.writeUnicodeString("name" + id);
                                }
                            }
                        }
                        preparedStatement.setObject(1, new MyClickHouseWriter(i));
                        preparedStatement.executeUpdate();
                        break;
                }

                System.out.printf("[%d] optimizeLevel=%d, insert batch [%d/%d] succeeded, cost %d ms\n",
                        Thread.currentThread().getId(), optimizeLevel, i + 1, INSERT_BATCH_NUM, System.currentTimeMillis() - insertStartTime);
            }
        } finally {
            if (preparedStatement != null) {
                preparedStatement.close();
            }
        }

    }

    /**
     * Count the table.
     * @param conn The ClickHouse connection.
     * @throws Exception
     */
    public static void count(Connection conn) throws Exception {
        try(Statement statement = conn.createStatement()) {
            ResultSet resultSet = statement.executeQuery("SELECT count() as cnt FROM `default`.`test`");
            if (resultSet.next()) {
                System.out.printf("Table `default`.`test` has %d rows\n", resultSet.getInt("cnt"));
            } else {
                throw new RuntimeException("Failed to count table `default`.`test`");
            }
        }
    }
}

Complete project sample description

Click awesome-clickhouse-jdbc-0.2.1.zip to download the sample code.

Project environment

  • Maven version: 3.9.6

  • JDK version: 1.8

Project structure

The following table describes the structure of this sample project.

image

File name

Description

awesome-clickhouse-jdbc-0.2.1

Project name.

mybatis-hikari-example

Subproject name.

  • Project features:

    • The project uses HikariCP as the database connection pool, MyBatis as the Object-Relational Mapping (ORM) framework, and standard JDBC for the persistence layer.

    • It has a complete project structure that includes an entity layer, a data access layer (mapper), and a service layer, which closely resembles actual project development.

  • Scenario: You want to use the MyBatis framework to access an ApsaraDB for ClickHouse cluster.

native-example

Subproject name.

  • Project features:

    • The project uses HikariCP as the database connection pool and standard JDBC for the persistence layer.

    • It contains only a `Main` class where all the logic resides.

  • Scenario: You want to learn how to connect to an ApsaraDB for ClickHouse cluster using JDBC or perform simple performance tests.

Usage instructions

mybatis-hikari-example

The overall code logic of this project is consistent with the native-example project. When you use the code, take note of the following parameters and the code entry point.

  • Database parameter configuration: src/main/resources/application.yml

  • Code entry point and other parameter configurations: src/main/java/com/aliyun/Main.java

The following table describes the parameters.

Location to modify

Parameter

Description

Example

src/main/resources/application.yml

url

The endpoint.

Format: jdbc:clickhouse:http://VPC_ENDPOINT:8123. `VPC_ENDPOINT` is the VPC endpoint or public endpoint of the cluster.

jdbc:clickhouse:http://cc-bp128o64g****ky35-clickhouse.clickhouseserver.rds.aliyuncs.com:8123

username

The database account.

test

password

The password of the database account.

Password****

src/main/java/com/aliyun/Main.java

INSERT_BATCH_SIZE

The number of data rows to insert in a batch. Unit: rows.

10000

INSERT_BATCH_NUM

The number of batches to insert. Unit: batches.

10

ENTERPRISE

The table engine to use.

`true`: Enterprise Edition cluster.

`false`: Community Edition cluster.

true

INSERT_OPTIMIZE_LEVEL

The optimization level for insert performance. Valid values: 1, 2, and 3.

The insert speed ranks as follows: 3 > 2 > 1.

3

native-example

The code entry point and all parameter configurations for this project are in src/main/java/com/aliyun/Main.java. For more information, see Step 3: Write the application code.

References

To log on to the cluster using other tools, see the following documents:

FAQ

Q: After I start the program, a connect timed out error is reported.

A: Troubleshoot the issue as follows:

  1. Check the whitelist: Ensure that the IP address of the server where the program resides is added to the whitelist of the destination cluster. For more information, see Set a whitelist.

  2. Check the network:

    Are the application and the destination cluster in the same VPC?

  3. Check the configured endpoint:

    • Ensure that the VPC endpoint or public endpoint is correct.

    • Ensure that the port is correct. The default port is 8123.

Q: After I start the program, a "java.sql.SQLException: Read timed out" error is reported. How do I resolve this issue?

A: Configure the TCP keepalive parameters for the operating system and set JDBC properties such as socket_keepalive=true and http_connection_provider=APACHE_HTTP_CLIENT, as shown in the native-example project. For more information, see Troubleshooting.

Q: The client reports an error similar to "java.sql.SQLException: HikariPool-1 - Connection is not available". How do I resolve this issue?

A: Close the connection after you are finished using it. For more information, see the Complete project sample.