Hologres is compatible with the PostgreSQL wire protocol, so any tool or application that uses the PostgreSQL JDBC Driver can connect to it. This guide walks you through setting up a JDBC connection, writing and querying data, and tuning performance.
On this page
Prerequisites
Before you begin, ensure that you have:
A Hologres instance with a database created
Your instance endpoint, port, and database name (available on the Instance Details page in the Hologres console, under Network Information)
Your AccessKey ID and AccessKey secret
Usage notes
Use PostgreSQL JDBC Driver 42.3.2 or later. This version fixes the CVE-2022-21724 vulnerability, so earlier versions carry a security risk.
For write performance tests, use a Virtual Private Cloud (VPC) network. The public network cannot meet performance testing benchmarks.
Hologres does not support mixing DDL and write operations in the same transaction. Run DDL and write operations in separate transactions. Mixing them returns the error
ERROR: INSERT in ddl transaction is not supported now.Keep
autoCommitset totrue. This is the JDBC default, so do not explicitly call commit in your code:Connection conn = DriverManager.getConnection(url, user, password); conn.setAutoCommit(true);Write operations in Hologres cannot be rolled back. Neither
Connection.rollback()nor the SQLROLLBACKstatement undoes an INSERT, UPDATE, or DELETE that has already run, and a write takes effect immediately — it is visible to other connections before commit.Do not rely on JDBC transactions for failure compensation. Implement idempotency or reconciliation in your application instead, for example by using
INSERT ON CONFLICTto make retries idempotent.
Connect to Hologres using JDBC
Step 1: Add the driver dependency
Most SQL client tools include a built-in PostgreSQL driver — use it if available. For Java applications, add the PostgreSQL JDBC Driver to your Maven project. Download it from jdbc.postgresql.org/download and use version 42.3.2 or later (the latest stable version is recommended).
Hologres uses the standard PostgreSQL JDBC Driver — there is no separate Hologres-specific driver to install.
Add the following dependency to your pom.xml:
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<!-- Use 42.3.2 or later. The latest stable version is recommended. -->
<version>42.7.13</version>
</dependency>
</dependencies>Step 2: Build the connection string
The connection string format is:
jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?user=<ACCESS_ID>&password=<ACCESS_KEY>Required parameters
Parameter | Description |
| The network address of the Hologres instance. In the console, the address and the port are provided together as a single value in the format |
| The port of the Hologres instance — the part after the colon in the Domain Name value described above. |
| The name of the database in Hologres. |
| Your AccessKey ID. Store it in an environment variable instead of hardcoding it. |
| Your AccessKey secret. Store it in an environment variable instead of hardcoding it. |
Optional parameters
Append these to the connection string as needed. Separate multiple parameters with &.
Parameter | Effect |
| Tags connections with your application name, making it easier to identify them in the slow query checklist. |
| Rewrites batch inserts into a single multi-value INSERT statement for higher write throughput. |
| Sets the default schema. Required when querying foreign tables after enabling automatic foreign table loading from MaxCompute — MaxCompute project names are mapped to schemas with the same names. |
| Switches the PostgreSQL JDBC driver from the extended protocol ( |
About preferQueryMode
Under the extended protocol, PreparedStatement parameters are sent with explicit types and the driver does not perform implicit type conversion, so comparing an integer column with a varchar parameter can fail with an error such as ERROR: operator does not exist: integer = character varying. If you encounter this type-mismatch error, add preferQueryMode=simple to the connection string. The driver then sends the SQL as plain text and lets the server infer parameter types from context.
With simple, the driver stops using server-side prepared statements, so the cached SQL compilation results that Prepared Statement mode relies on are lost. Throughput drops noticeably for high-frequency queries and batch writes, which conflicts with the recommendation elsewhere in this topic to use Prepared Statement mode for higher throughput.
Resolve type-mismatch errors in the following order of preference:
Use the setter that matches the column type — for example,
setIntinstead ofsetStringfor an INT column.Or cast the parameter explicitly in SQL, such as
where id = ?::int.Use
preferQueryMode=simpleonly when you cannot change the calling code, such as with a third-party ORM or BI tool. If you also need batch write performance, you can combine it withreWriteBatchedInserts=true— the two work together.
Example connection string with recommended options:
jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?user=<ACCESS_ID>&password=<ACCESS_KEY>&ApplicationName=myApp&reWriteBatchedInserts=trueIf a PreparedStatement query fails with an error such as ERROR: operator does not exist: integer = character varying due to a type mismatch between the query parameter and the column, add preferQueryMode=simple to the connection string:
jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?user=<ACCESS_ID>&password=<ACCESS_KEY>&preferQueryMode=simpleStep 3: Store credentials in environment variables
Hardcoding credentials in connection strings creates a security risk. Store them as environment variables instead. On Linux, add the following to your ~/.bash_profile:
export ALIBABA_CLOUD_USER=<ACCESS_ID>
export ALIBABA_CLOUD_PASSWORD=<ACCESS_KEY>Step 4: Connect and run a query
To prevent connections from hanging, configure socketTimeout, loginTimeout, and tcpKeepAlive — exposed as SOCKET_TIMEOUT, LOGIN_TIMEOUT, and TCP_KEEP_ALIVE in PGProperty. See the following example.
The following example reads credentials from environment variables, sets timeout properties, connects to Hologres, and runs a basic SELECT query using a standard Statement:
import org.postgresql.PGProperty;
import java.sql.*;
import java.util.Properties;
public class HologresTest {
private void jdbcExample() throws SQLException {
String user = System.getenv("ALIBABA_CLOUD_USER");
String password = System.getenv("ALIBABA_CLOUD_PASSWORD");
String url = String.format(
"jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?currentSchema=<SCHEMA_NAME>&user=%s&password=%s",
user, password
);
Properties props = new Properties();
// Set SOCKET_TIMEOUT based on actual query execution time to avoid
// premature timeout before the query finishes.
PGProperty.SOCKET_TIMEOUT.set(props, 3600);
PGProperty.LOGIN_TIMEOUT.set(props, 60);
PGProperty.TCP_KEEP_ALIVE.set(props, true);
try (Connection conn = DriverManager.getConnection(url, props)) {
try (Statement st = conn.createStatement()) {
String sql = "SELECT pk, f1 FROM test_tb WHERE pk > 0 LIMIT 100";
try (ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) {
// Read the first column value
String c1 = rs.getString(1);
}
}
}
}
}
}Write and query data
Write data
You can write data using the Statement or Prepared Statement mode in JDBC. Use Prepared Statement mode for write operations. In this mode, the server caches SQL compilation results, which reduces write latency and increases throughput. Set the batch size to a multiple of 256 — the minimum recommended batch size is 256.
The examples in this section use the following table. Create it in your database first.
CREATE TABLE test_tb (
pk int PRIMARY KEY,
f1 text,
f2 timestamptz,
f3 double precision
);Batch insert
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.text.SimpleDateFormat;
/*
* Write data in batches using Prepared Statement mode.
* Batch size: 256 rows (minimum recommended).
*/
private static void writeBatchWithPreparedStatement(Connection conn) throws Exception {
try (PreparedStatement stmt = conn.prepareStatement("insert into test_tb values (?,?,?,?)")) {
int batchSize = 256;
for (int i = 0; i < batchSize; ++i) {
stmt.setInt(1, 1000 + i);
stmt.setString(2, "1");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
java.util.Date parsedDate = dateFormat.parse("1990-11-11 00:00:00");
stmt.setTimestamp(3, new java.sql.Timestamp(parsedDate.getTime()));
stmt.setDouble(4, 0.1);
stmt.addBatch();
}
stmt.executeBatch();
}
}Upsert with INSERT ON CONFLICT
To update existing rows on conflict, use the PostgreSQL INSERT ON CONFLICT syntax. The destination table must have a primary key.
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.text.SimpleDateFormat;
private static void insertOverwrite(Connection conn) throws Exception {
try (PreparedStatement stmt = conn.prepareStatement(
"insert into test_tb values (?,?,?,?), (?,?,?,?), (?,?,?,?), (?,?,?,?), (?,?,?,?), (?,?,?,?) " +
"on conflict(pk) do update set f1 = excluded.f1, f2 = excluded.f2, f3 = excluded.f3"
)) {
int batchSize = 6;
for (int i = 0; i < batchSize; ++i) {
stmt.setInt(i * 4 + 1, i);
stmt.setString(i * 4 + 2, "1");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
java.util.Date parsedDate = dateFormat.parse("1990-11-11 00:00:00");
stmt.setTimestamp(i * 4 + 3, new java.sql.Timestamp(parsedDate.getTime()));
stmt.setDouble(i * 4 + 4, 0.1);
}
int affectedRows = stmt.executeUpdate();
System.out.println("affected rows => " + affectedRows);
}
}The following example uses Prepared Statement mode for write operations, which improves throughput for repeated inserts:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
private void jdbcPreparedStmtExample() throws SQLException {
String user = System.getenv("ALIBABA_CLOUD_USER");
String password = System.getenv("ALIBABA_CLOUD_PASSWORD");
String url = String.format(
"jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?currentSchema=<SCHEMA_NAME>&user=%s&password=%s",
user, password
);
try (Connection conn = DriverManager.getConnection(url)) {
String sql = "insert into test values" +
"(?, ?), (?, ?), (?, ?), (?, ?), (?, ?), " +
"(?, ?), (?, ?), (?, ?), (?, ?), (?, ?)";
try (PreparedStatement st = conn.prepareStatement(sql)) {
for (int i = 0; i < 10; ++i) {
for (int j = 0; j < 2 * 10; ++j) {
st.setString(j + 1, UUID.randomUUID().toString());
}
System.out.println("affected row => " + st.executeUpdate());
}
}
}
}Query data
Use standard SQL SELECT statements to query data from Hologres tables. The basic SELECT example in Step 4 demonstrates this pattern.
For parameterized queries, use Prepared Statement mode and pass parameters with the setter that matches the column type:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
private static void queryWithPreparedStatement(Connection conn) throws Exception {
String sql = "select pk, f1, f2, f3 from test_tb where pk = ?";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
// pk is an INT column, so use setInt. Passing a String with setString
// fails under the extended protocol with:
// ERROR: operator does not exist: integer = character varying
stmt.setInt(1, 1000);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getInt("pk") + ", " + rs.getString("f1"));
}
}
}
}Configure a Druid connection pool
Use Druid 1.1.12 or later to connect to Hologres.
Usage notes:
Set
keepAlive=trueto reuse connections and avoid connection churn.Druid versions 1.2.12 through 1.2.21 have a known issue where
connectTimeoutandsocketTimeoutdefault to 10 seconds when not specified. Upgrade to a newer version if you encounter this.Set
initialSize,minIdle, andmaxActivebased on your instance size and workload.
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<!-- Endpoint URL from the instance configuration page in the console -->
<property name="url" value="${jdbc_url}" />
<!-- AccessKey ID of the user account -->
<property name="username" value="${jdbc_user}" />
<!-- AccessKey secret of the user account -->
<property name="password" value="${jdbc_password}" />
<!-- Pool sizing: adjust based on instance size and workload -->
<property name="initialSize" value="5" />
<property name="minIdle" value="10" />
<property name="maxActive" value="20" />
<!-- Wait up to 60 seconds for a connection from the pool -->
<property name="maxWait" value="60000" />
<!-- Check for idle connections every 2 seconds -->
<property name="timeBetweenEvictionRunsMillis" value="2000" />
<!-- Evict connections idle for more than 10 minutes -->
<property name="minEvictableIdleTimeMillis" value="600000" />
<property name="maxEvictableIdleTimeMillis" value="900000" />
<property name="validationQuery" value="select 1" />
<property name="testWhileIdle" value="true" />
<!-- Do not test connections on borrow/return (reduces overhead) -->
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="keepAlive" value="true" />
<property name="phyMaxUseCount" value="100000" />
<property name="filters" value="stat" />
</bean>Set GUC parameters
Grand Unified Configuration (GUC) parameters control session-level behavior such as timeouts. Set them at connection time using PGProperty.OPTIONS.
The following example sets statement_timeout and idle_in_transaction_session_timeout to 12,345 milliseconds:
import org.postgresql.PGProperty;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class GucDemo {
public static void main(String[] args) {
String hostname = "hgpostcn-cn-xxxx-cn-hangzhou.hologres.aliyuncs.com";
String port = "80";
String dbname = "demo";
String jdbcUrl = "jdbc:postgresql://" + hostname + ":" + port + "/" + dbname;
Properties properties = new Properties();
properties.setProperty("user", "xxxxx");
properties.setProperty("password", "xxxx");
// Set session-level GUC parameters
PGProperty.OPTIONS.set(properties,
"--statement_timeout=12345 --idle_in_transaction_session_timeout=12345");
try {
Class.forName("org.postgresql.Driver");
Connection connection = DriverManager.getConnection(jdbcUrl, properties);
PreparedStatement preparedStatement =
connection.prepareStatement("show statement_timeout");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
ResultSetMetaData rsmd = resultSet.getMetaData();
int columnCount = rsmd.getColumnCount();
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < columnCount; i++) {
map.put(rsmd.getColumnName(i + 1).toLowerCase(), resultSet.getObject(i + 1));
}
System.out.println(map);
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
}For the full list of available GUC parameters, see GUC parameters.
Performance tuning
Apply the following practices to maximize write throughput:
Use a VPC network. The public network introduces latency that prevents achieving optimal write performance.
Enable batch rewriting. Add
reWriteBatchedInserts=trueto the connection string. This rewrites individual inserts into a single multi-value statement, significantly increasing throughput.Use Prepared Statement mode. The server caches SQL compilation results, reducing per-row latency.
Set batch size to a multiple of 256. The minimum effective batch size is 256. Larger multiples yield further throughput gains. For automated batching, use Holo Client.
Example connection string with all performance options enabled:
jdbc:postgresql://<ENDPOINT>:<PORT>/<DBNAME>?ApplicationName=<APPLICATION_NAME>&reWriteBatchedInserts=trueLoad balancing
Starting from Hologres V1.3, you can configure multiple read-only secondary instances in JDBC to distribute read workloads. For setup instructions, see JDBC-based load balancing.