{@code useHttp2} and {@code customHeaders} have no equivalent in the v5 gRPC driver and are
+ * kept for source compatibility only (they are ignored at runtime).
+ */
+public class NebulaPoolConfig implements Serializable {
+
+ private static final long serialVersionUID = 3977910115039279651L;
+
+ private int minConnsSize = 0;
+ private int maxConnsSize = 10;
+ private int timeout = 0;
+ private int idleTime = 0;
+ private int intervalIdle = -1;
+ private int waitTime = 0;
+ private double minClusterHealthRate = 1;
+ private boolean enableSsl = false;
+ private SSLParam sslParam = null;
+ private boolean useHttp2 = false;
+ private Map customHeaders = new HashMap<>();
+
+ public boolean isEnableSsl() {
+ return enableSsl;
+ }
+
+ public void setEnableSsl(boolean enableSsl) {
+ this.enableSsl = enableSsl;
+ }
+
+ public SSLParam getSslParam() {
+ return sslParam;
+ }
+
+ public void setSslParam(SSLParam sslParam) {
+ this.sslParam = sslParam;
+ }
+
+ public int getMinConnSize() {
+ return minConnsSize;
+ }
+
+ public NebulaPoolConfig setMinConnSize(int minConnSize) {
+ this.minConnsSize = minConnSize;
+ return this;
+ }
+
+ public int getMaxConnSize() {
+ return maxConnsSize;
+ }
+
+ public NebulaPoolConfig setMaxConnSize(int maxConnSize) {
+ this.maxConnsSize = maxConnSize;
+ return this;
+ }
+
+ public int getTimeout() {
+ return timeout;
+ }
+
+ public NebulaPoolConfig setTimeout(int timeout) {
+ this.timeout = timeout;
+ return this;
+ }
+
+ public int getIdleTime() {
+ return idleTime;
+ }
+
+ public NebulaPoolConfig setIdleTime(int idleTime) {
+ this.idleTime = idleTime;
+ return this;
+ }
+
+ public int getIntervalIdle() {
+ return intervalIdle;
+ }
+
+ public NebulaPoolConfig setIntervalIdle(int intervalIdle) {
+ this.intervalIdle = intervalIdle;
+ return this;
+ }
+
+ public int getWaitTime() {
+ return waitTime;
+ }
+
+ public NebulaPoolConfig setWaitTime(int waitTime) {
+ this.waitTime = waitTime;
+ return this;
+ }
+
+ public double getMinClusterHealthRate() {
+ return minClusterHealthRate;
+ }
+
+ public NebulaPoolConfig setMinClusterHealthRate(double minClusterHealthRate) {
+ this.minClusterHealthRate = minClusterHealthRate;
+ return this;
+ }
+
+ public boolean isUseHttp2() {
+ return useHttp2;
+ }
+
+ public NebulaPoolConfig setUseHttp2(boolean useHttp2) {
+ this.useHttp2 = useHttp2;
+ return this;
+ }
+
+ public Map getCustomHeaders() {
+ return customHeaders;
+ }
+
+ public NebulaPoolConfig setCustomHeaders(Map customHeaders) {
+ this.customHeaders = customHeaders;
+ return this;
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/NebulaSession.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/NebulaSession.java
new file mode 100644
index 000000000..cb0e17943
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/NebulaSession.java
@@ -0,0 +1,119 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph;
+
+import com.vesoft.nebula.driver.v3client.graph.data.ResultSet;
+import com.vesoft.nebula.driver.v3client.graph.exception.IOErrorException;
+import com.vesoft.nebula.driver.v3client.graph.net.Session;
+import com.vesoft.nebula.driver.v3client.graph.net.SessionState;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A pooled session, matching the v3 client's {@code NebulaSession}.
+ *
+ *
Each instance wraps one v5 driver {@link com.vesoft.nebula.driver.graph.net.NebulaClient}
+ * (one server-side session).
+ */
+public class NebulaSession implements Serializable {
+
+ private static final long serialVersionUID = -88438249377120255L;
+
+ private final long sessionID;
+ private final int timezoneOffset;
+
+ private volatile com.vesoft.nebula.driver.graph.net.NebulaClient client;
+ private final AtomicReference state = new AtomicReference<>();
+ private final AtomicBoolean isReleased = new AtomicBoolean(false);
+
+ public NebulaSession(com.vesoft.nebula.driver.graph.net.NebulaClient client,
+ SessionState state) {
+ this.client = client;
+ this.sessionID = client.getSessionId();
+ this.timezoneOffset = 0;
+ this.state.set(state);
+ }
+
+ public long getSessionID() {
+ return sessionID;
+ }
+
+ public Boolean isIdle() {
+ return state.get() == SessionState.IDLE;
+ }
+
+ public Boolean isUsed() {
+ return state.get() == SessionState.USED;
+ }
+
+ public boolean isUsedAndSetIdle() {
+ return state.compareAndSet(SessionState.USED, SessionState.IDLE);
+ }
+
+ public boolean isIdleAndSetUsed() {
+ return state.compareAndSet(SessionState.IDLE, SessionState.USED);
+ }
+
+ public ResultSet execute(String stmt) throws IOErrorException {
+ return executeWithParameter(stmt, Collections.emptyMap());
+ }
+
+ public ResultSet executeWithParameter(String stmt, Map parameterMap)
+ throws IOErrorException {
+ checkReleased();
+ String gql = Session.inlineParameters(stmt, parameterMap);
+ try {
+ return new ResultSet(client.execute(gql), timezoneOffset);
+ } catch (com.vesoft.nebula.driver.graph.exception.IOErrorException e) {
+ throw Session.toCompat(e);
+ }
+ }
+
+ public ResultSet executeWithTimeout(String stmt, long timeoutMs) throws IOErrorException {
+ return executeWithParameterTimeout(stmt, Collections.emptyMap(), timeoutMs);
+ }
+
+ public ResultSet executeWithParameterTimeout(String stmt,
+ Map parameterMap,
+ long timeoutMs) throws IOErrorException {
+ checkReleased();
+ if (timeoutMs <= 0) {
+ throw new IllegalArgumentException("timeout should be a positive number");
+ }
+ String gql = Session.inlineParameters(stmt, parameterMap);
+ try {
+ return new ResultSet(client.execute(gql, timeoutMs), timezoneOffset);
+ } catch (com.vesoft.nebula.driver.graph.exception.IOErrorException e) {
+ throw Session.toCompat(e);
+ }
+ }
+
+ public String executeJsonWithParameter(String stmt, Map parameterMap)
+ throws IOErrorException {
+ return Session.toJson(executeWithParameter(stmt, parameterMap));
+ }
+
+ public void release() {
+ if (isReleased.compareAndSet(false, true)) {
+ try {
+ client.close();
+ } catch (Exception e) {
+ // ignore; the connection is being released anyway.
+ }
+ client = null;
+ }
+ }
+
+ private void checkReleased() throws IOErrorException {
+ if (client == null) {
+ throw new IOErrorException(IOErrorException.E_CONNECT_BROKEN,
+ "The session was released, couldn't use again.");
+ }
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/SessionPool.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/SessionPool.java
new file mode 100644
index 000000000..c02a44f8d
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/SessionPool.java
@@ -0,0 +1,484 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph;
+
+import com.alibaba.fastjson.JSON;
+import com.vesoft.nebula.driver.v3client.graph.data.CASignedSSLParam;
+import com.vesoft.nebula.driver.v3client.graph.data.HostAddress;
+import com.vesoft.nebula.driver.v3client.graph.data.ResultSet;
+import com.vesoft.nebula.driver.v3client.graph.data.SSLParam;
+import com.vesoft.nebula.driver.v3client.graph.data.SelfSignedSSLParam;
+import com.vesoft.nebula.driver.v3client.graph.exception.AuthFailedException;
+import com.vesoft.nebula.driver.v3client.graph.exception.BindSpaceFailedException;
+import com.vesoft.nebula.driver.v3client.graph.exception.ClientServerIncompatibleException;
+import com.vesoft.nebula.driver.v3client.graph.exception.IOErrorException;
+import com.vesoft.nebula.driver.v3client.graph.net.Session;
+import com.vesoft.nebula.driver.v3client.graph.net.SessionState;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A pool of sessions, matching the v3 client's {@code SessionPool}.
+ */
+public class SessionPool implements Serializable {
+
+ private static final long serialVersionUID = 6051248334277617891L;
+
+ private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+ private final ScheduledExecutorService healthCheckSchedule =
+ Executors.newScheduledThreadPool(1);
+ private final ScheduledExecutorService sessionQueueMaintainSchedule =
+ Executors.newScheduledThreadPool(1);
+
+ public CopyOnWriteArrayList sessionList = new CopyOnWriteArrayList<>();
+ public AtomicInteger idleSessionSize = new AtomicInteger(0);
+ public AtomicBoolean hasInit = new AtomicBoolean(false);
+ public AtomicBoolean isClosed = new AtomicBoolean(false);
+
+ private final AtomicInteger pos = new AtomicInteger(0);
+
+ private final SessionPoolConfig sessionPoolConfig;
+ private final int minSessionSize;
+ private final int maxSessionSize;
+ private final int cleanTime;
+ private final int healthCheckTime;
+ private final int retryTimes;
+ private final int intervalTime;
+ private final boolean reconnect;
+ private final String spaceName;
+
+ public SessionPool(SessionPoolConfig poolConfig) {
+ this.sessionPoolConfig = poolConfig;
+ this.minSessionSize = poolConfig.getMinSessionSize();
+ this.maxSessionSize = poolConfig.getMaxSessionSize();
+ this.cleanTime = poolConfig.getCleanTime();
+ this.retryTimes = poolConfig.getRetryTimes();
+ this.intervalTime = poolConfig.getIntervalTime();
+ this.reconnect = poolConfig.isReconnect();
+ this.healthCheckTime = poolConfig.getHealthCheckTime();
+ this.spaceName = poolConfig.getSpaceName();
+ init();
+ }
+
+ private synchronized NebulaSession getSession()
+ throws ClientServerIncompatibleException, AuthFailedException, IOErrorException,
+ BindSpaceFailedException {
+ int retry = sessionPoolConfig.getRetryConnectTimes();
+ while (retry-- >= 0) {
+ if (idleSessionSize.get() > 0) {
+ for (NebulaSession nebulaSession : sessionList) {
+ if (nebulaSession.isIdleAndSetUsed()) {
+ idleSessionSize.decrementAndGet();
+ return nebulaSession;
+ }
+ }
+ }
+ if (sessionList.size() < maxSessionSize) {
+ return createSessionObject(SessionState.USED);
+ }
+ try {
+ Thread.sleep(sessionPoolConfig.getWaitTime());
+ } catch (InterruptedException e) {
+ log.error("getSession error when wait for idle sessions, ", e);
+ throw new RuntimeException(e);
+ }
+ }
+ throw new RuntimeException("no extra session available");
+ }
+
+ @Deprecated
+ public boolean init() {
+ if (hasInit.get()) {
+ return true;
+ }
+ while (sessionList.size() < minSessionSize) {
+ try {
+ createSessionObject(SessionState.IDLE);
+ idleSessionSize.incrementAndGet();
+ } catch (Exception e) {
+ log.error("SessionPool init failed. ", e);
+ throw new RuntimeException("create session failed.", e);
+ }
+ }
+ healthCheckSchedule.scheduleAtFixedRate(this::checkSession, 0, healthCheckTime,
+ TimeUnit.SECONDS);
+ sessionQueueMaintainSchedule.scheduleAtFixedRate(this::updateSessionQueue, 0, cleanTime,
+ TimeUnit.SECONDS);
+ hasInit.compareAndSet(false, true);
+ return true;
+ }
+
+ public ResultSet execute(String stmt) throws IOErrorException,
+ ClientServerIncompatibleException, AuthFailedException, BindSpaceFailedException {
+ return execute(stmt, Collections.emptyMap());
+ }
+
+ public ResultSet execute(String stmt, Map parameterMap)
+ throws ClientServerIncompatibleException, AuthFailedException,
+ IOErrorException, BindSpaceFailedException {
+ stmtCheck(stmt);
+ checkSessionPool();
+ NebulaSession nebulaSession = null;
+ ResultSet resultSet = null;
+ int tryTimes = 0;
+ while (tryTimes++ <= retryTimes) {
+ try {
+ nebulaSession = getSession();
+ resultSet = nebulaSession.executeWithParameter(stmt, parameterMap);
+ if (resultSet.isSucceeded()
+ || resultSet.getErrorCode() == ErrorCode.E_SEMANTIC_ERROR.getValue()
+ || resultSet.getErrorCode() == ErrorCode.E_SYNTAX_ERROR.getValue()
+ || resultSet.getErrorCode() == ErrorCode.E_QUERY_TIMEDOUT.getValue()) {
+ releaseSession(nebulaSession);
+ return resultSet;
+ }
+ log.warn(String.format("execute error, code: %d, message: %s, retry: %d",
+ resultSet.getErrorCode(), resultSet.getErrorMessage(),
+ tryTimes));
+ nebulaSession.release();
+ sessionList.remove(nebulaSession);
+ try {
+ Thread.sleep(intervalTime);
+ } catch (InterruptedException ignored) {
+ // ignore
+ }
+ } catch (ClientServerIncompatibleException e) {
+ // will never get here.
+ } catch (AuthFailedException | BindSpaceFailedException e) {
+ throw e;
+ } catch (IOErrorException e) {
+ if (nebulaSession != null) {
+ nebulaSession.release();
+ sessionList.remove(nebulaSession);
+ }
+ if (tryTimes < retryTimes) {
+ log.warn(String.format("execute failed for IOErrorException, message: %s, "
+ + "retry: %d", e.getMessage(), tryTimes));
+ try {
+ Thread.sleep(intervalTime);
+ } catch (InterruptedException ignored) {
+ // ignore
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
+ if (nebulaSession != null) {
+ nebulaSession.release();
+ sessionList.remove(nebulaSession);
+ }
+ return resultSet;
+ }
+
+ public ResultSet executeWithTimeout(String stmt, long timeoutMs)
+ throws IOErrorException, AuthFailedException, BindSpaceFailedException {
+ return executeWithParameterTimeout(stmt, Collections.emptyMap(), timeoutMs);
+ }
+
+ public ResultSet executeWithParameterTimeout(String stmt,
+ Map parameterMap,
+ long timeoutMs)
+ throws IOErrorException, AuthFailedException, BindSpaceFailedException {
+ if (timeoutMs <= 0) {
+ throw new IllegalArgumentException("timeout should be a positive number");
+ }
+ stmtCheck(stmt);
+ checkSessionPool();
+ NebulaSession nebulaSession = null;
+ ResultSet resultSet = null;
+ int tryTimes = 0;
+ while (tryTimes++ <= retryTimes) {
+ try {
+ nebulaSession = getSession();
+ resultSet = nebulaSession.executeWithParameterTimeout(stmt, parameterMap, timeoutMs);
+ if (resultSet.isSucceeded()
+ || resultSet.getErrorCode() == ErrorCode.E_SEMANTIC_ERROR.getValue()
+ || resultSet.getErrorCode() == ErrorCode.E_SYNTAX_ERROR.getValue()
+ || resultSet.getErrorCode() == ErrorCode.E_QUERY_TIMEDOUT.getValue()) {
+ releaseSession(nebulaSession);
+ return resultSet;
+ }
+ log.warn(String.format("execute error, code: %d, message: %s, retry: %d",
+ resultSet.getErrorCode(), resultSet.getErrorMessage(),
+ tryTimes));
+ nebulaSession.release();
+ sessionList.remove(nebulaSession);
+ try {
+ Thread.sleep(intervalTime);
+ } catch (InterruptedException ignored) {
+ // ignore
+ }
+ } catch (ClientServerIncompatibleException e) {
+ // will never get here.
+ } catch (AuthFailedException | BindSpaceFailedException e) {
+ throw e;
+ } catch (IOErrorException e) {
+ if (nebulaSession != null) {
+ nebulaSession.release();
+ sessionList.remove(nebulaSession);
+ }
+ if (tryTimes < retryTimes) {
+ log.warn(String.format("execute failed for IOErrorException, message: %s, "
+ + "retry: %d", e.getMessage(), tryTimes));
+ try {
+ Thread.sleep(intervalTime);
+ } catch (InterruptedException ignored) {
+ // ignore
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
+ if (nebulaSession != null) {
+ nebulaSession.release();
+ sessionList.remove(nebulaSession);
+ }
+ return resultSet;
+ }
+
+ public String executeJson(String stmt)
+ throws ClientServerIncompatibleException, AuthFailedException,
+ IOErrorException, BindSpaceFailedException {
+ return executeJsonWithParameter(stmt, Collections.emptyMap());
+ }
+
+ public String executeJsonWithParameter(String stmt, Map parameterMap)
+ throws ClientServerIncompatibleException, AuthFailedException,
+ IOErrorException, BindSpaceFailedException {
+ stmtCheck(stmt);
+ checkSessionPool();
+ NebulaSession nebulaSession = getSession();
+ String result;
+ try {
+ result = nebulaSession.executeJsonWithParameter(stmt, parameterMap);
+ if (isSessionErrorForJson(result)) {
+ sessionList.remove(nebulaSession);
+ nebulaSession = getSession();
+ result = nebulaSession.executeJsonWithParameter(stmt, parameterMap);
+ }
+ } catch (IOErrorException e) {
+ if (nebulaSession != null) {
+ nebulaSession.release();
+ sessionList.remove(nebulaSession);
+ }
+ throw e;
+ }
+ releaseSession(nebulaSession);
+ return result;
+ }
+
+ public void close() {
+ if (isClosed.get()) {
+ return;
+ }
+ if (isClosed.compareAndSet(false, true)) {
+ for (NebulaSession nebulaSession : sessionList) {
+ nebulaSession.release();
+ }
+ sessionList.clear();
+ if (!healthCheckSchedule.isShutdown()) {
+ healthCheckSchedule.shutdown();
+ }
+ if (!sessionQueueMaintainSchedule.isShutdown()) {
+ sessionQueueMaintainSchedule.shutdown();
+ }
+ }
+ }
+
+ public boolean isActive() {
+ return hasInit.get();
+ }
+
+ public boolean isClosed() {
+ return isClosed.get();
+ }
+
+ public int getSessionNums() {
+ return sessionList.size();
+ }
+
+ public int getIdleSessionNums() {
+ return idleSessionSize.get();
+ }
+
+ private void releaseSession(NebulaSession nebulaSession) {
+ nebulaSession.isUsedAndSetIdle();
+ idleSessionSize.incrementAndGet();
+ }
+
+ private void checkSession() {
+ for (NebulaSession nebulaSession : sessionList) {
+ if (nebulaSession.isIdleAndSetUsed()) {
+ try {
+ idleSessionSize.decrementAndGet();
+ nebulaSession.execute("RETURN 1");
+ nebulaSession.isUsedAndSetIdle();
+ idleSessionSize.incrementAndGet();
+ } catch (IOErrorException e) {
+ log.error("session ping error, {}, remove current session.", e.getMessage());
+ nebulaSession.release();
+ sessionList.remove(nebulaSession);
+ }
+ }
+ }
+ }
+
+ private void updateSessionQueue() {
+ if (idleSessionSize.get() > minSessionSize) {
+ synchronized (this) {
+ for (NebulaSession nebulaSession : sessionList) {
+ if (nebulaSession.isIdle()) {
+ nebulaSession.release();
+ sessionList.remove(nebulaSession);
+ if (idleSessionSize.decrementAndGet() <= minSessionSize) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private NebulaSession createSessionObject(SessionState state)
+ throws ClientServerIncompatibleException, AuthFailedException,
+ IOErrorException, BindSpaceFailedException {
+ com.vesoft.nebula.driver.graph.net.NebulaClient client = buildClient();
+
+ NebulaSession nebulaSession = new NebulaSession(client, state);
+ ResultSet result;
+ try {
+ result = nebulaSession.execute(
+ String.format("SESSION SET GRAPH \"%s\"", spaceName));
+ } catch (IOErrorException e) {
+ log.error("binding graph failed,", e);
+ nebulaSession.release();
+ throw new BindSpaceFailedException("binding graph failed:" + e.getMessage());
+ }
+ if (!result.isSucceeded()) {
+ nebulaSession.release();
+ throw new BindSpaceFailedException(result.getErrorMessage());
+ }
+ sessionList.add(nebulaSession);
+ return nebulaSession;
+ }
+
+ private com.vesoft.nebula.driver.graph.net.NebulaClient buildClient()
+ throws AuthFailedException, IOErrorException {
+ StringBuilder sb = new StringBuilder();
+ for (HostAddress address : sessionPoolConfig.getGraphAddressList()) {
+ if (sb.length() > 0) {
+ sb.append(',');
+ }
+ sb.append(address.toString());
+ }
+ com.vesoft.nebula.driver.graph.net.NebulaClient.Builder builder =
+ com.vesoft.nebula.driver.graph.net.NebulaClient.builder(
+ sb.toString(), sessionPoolConfig.getUsername(), sessionPoolConfig.getPassword());
+ if (sessionPoolConfig.getTimeout() > 0) {
+ builder.withConnectTimeoutMills(sessionPoolConfig.getTimeout());
+ builder.withRequestTimeoutMills(sessionPoolConfig.getTimeout());
+ }
+ applyTls(builder, sessionPoolConfig);
+ try {
+ return builder.build();
+ } catch (com.vesoft.nebula.driver.graph.exception.AuthFailedException e) {
+ throw Session.toCompatAuth(e);
+ } catch (com.vesoft.nebula.driver.graph.exception.IOErrorException e) {
+ throw Session.toCompat(e);
+ }
+ }
+
+ private void applyTls(com.vesoft.nebula.driver.graph.net.NebulaClient.Builder builder,
+ SessionPoolConfig config) {
+ if (!config.isEnableSsl()) {
+ return;
+ }
+ builder.withEnableTls(true);
+ SSLParam ssl = config.getSslParam();
+ if (ssl == null) {
+ builder.withDisableVerifyServerCert(true);
+ return;
+ }
+ if (ssl.isSkipVerifyServer()) {
+ builder.withDisableVerifyServerCert(true);
+ }
+ if (ssl instanceof CASignedSSLParam) {
+ CASignedSSLParam ca = (CASignedSSLParam) ssl;
+ if (ca.getCaCrtFilePath() != null) {
+ builder.withTlsCa(ca.getCaCrtFilePath());
+ }
+ if (ca.getCrtFilePath() != null && ca.getKeyFilePath() != null) {
+ builder.withTlsCert(ca.getCrtFilePath(), ca.getKeyFilePath());
+ }
+ } else if (ssl instanceof SelfSignedSSLParam) {
+ SelfSignedSSLParam self = (SelfSignedSSLParam) ssl;
+ builder.withDisableVerifyServerCert(true);
+ if (self.getCrtFilePath() != null && self.getKeyFilePath() != null) {
+ builder.withTlsCert(self.getCrtFilePath(), self.getKeyFilePath());
+ }
+ }
+ }
+
+ public HostAddress getAddress() {
+ List addresses = sessionPoolConfig.getGraphAddressList();
+ int newPos = (pos.getAndIncrement()) % addresses.size();
+ return addresses.get(newPos);
+ }
+
+ private boolean isSessionError(ResultSet resultSet) {
+ return resultSet != null
+ && (resultSet.getErrorCode() == ErrorCode.E_SESSION_INVALID.getValue()
+ || resultSet.getErrorCode() == ErrorCode.E_SESSION_NOT_FOUND.getValue()
+ || resultSet.getErrorCode() == ErrorCode.E_SESSION_TIMEOUT.getValue());
+ }
+
+ private boolean isSessionErrorForJson(String result) {
+ if (result == null) {
+ return true;
+ }
+ int code = JSON.parseObject(result).getJSONArray("errors")
+ .getJSONObject(0).getIntValue("code");
+ return code == ErrorCode.E_SESSION_INVALID.getValue()
+ || code == ErrorCode.E_SESSION_NOT_FOUND.getValue()
+ || code == ErrorCode.E_SESSION_TIMEOUT.getValue();
+ }
+
+ private void checkSessionPool() {
+ if (!hasInit.get()) {
+ throw new RuntimeException("The SessionPool has not been initialized, "
+ + "please call init() first.");
+ }
+ if (isClosed.get()) {
+ throw new RuntimeException("The SessionPool has been closed.");
+ }
+ }
+
+ private void stmtCheck(String stmt) {
+ if (stmt == null || stmt.trim().isEmpty()) {
+ throw new IllegalArgumentException("statement is null.");
+ }
+ String trimmed = stmt.trim();
+ if (trimmed.toLowerCase().startsWith("use ")) {
+ throw new IllegalArgumentException("`USE SPACE`/`USE GRAPH` alone is forbidden.");
+ }
+ if (trimmed.toLowerCase().startsWith("session set graph")) {
+ throw new IllegalArgumentException("`SESSION SET GRAPH` alone is forbidden.");
+ }
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/SessionPoolConfig.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/SessionPoolConfig.java
new file mode 100644
index 000000000..469b127ed
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/SessionPoolConfig.java
@@ -0,0 +1,254 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph;
+
+import com.vesoft.nebula.driver.v3client.graph.data.HostAddress;
+import com.vesoft.nebula.driver.v3client.graph.data.SSLParam;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Session-pool configuration, matching the v3 client.
+ */
+public class SessionPoolConfig implements Serializable {
+
+ private static final long serialVersionUID = -2266013330384849132L;
+
+ private final List graphAddressList;
+
+ private final String username;
+ private final String password;
+ private final String spaceName;
+
+ private int minSessionSize = 1;
+ private int maxSessionSize = 10;
+ private int timeout = 0;
+ private int cleanTime = 3600;
+ private int healthCheckTime = 600;
+ private int retryConnectTimes = 1;
+ private int waitTime = 0;
+ private int retryTimes = 3;
+ private int intervalTime = 0;
+ private boolean reconnect = false;
+ private boolean enableSsl = false;
+ private SSLParam sslParam = null;
+ private boolean useHttp2 = false;
+ private Map customHeaders = new HashMap<>();
+
+ public SessionPoolConfig(List addresses,
+ String spaceName,
+ String username,
+ String password) {
+ if (addresses == null || addresses.size() == 0) {
+ throw new IllegalArgumentException("Graph addresses cannot be empty.");
+ }
+ if (spaceName == null || spaceName.trim().isEmpty()) {
+ throw new IllegalArgumentException("space name cannot be blank.");
+ }
+ if (username == null || username.trim().isEmpty()) {
+ throw new IllegalArgumentException("user name cannot be blank.");
+ }
+ if (password == null || password.trim().isEmpty()) {
+ throw new IllegalArgumentException("password cannot be blank.");
+ }
+ this.graphAddressList = addresses;
+ this.spaceName = spaceName;
+ this.username = username;
+ this.password = password;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public List getGraphAddressList() {
+ return graphAddressList;
+ }
+
+ public String getSpaceName() {
+ return spaceName;
+ }
+
+ public int getMinSessionSize() {
+ return minSessionSize;
+ }
+
+ public SessionPoolConfig setMinSessionSize(int minSessionSize) {
+ if (minSessionSize < 1) {
+ throw new IllegalArgumentException("minSessionSize cannot be less than 1.");
+ }
+ this.minSessionSize = minSessionSize;
+ return this;
+ }
+
+ public int getMaxSessionSize() {
+ return maxSessionSize;
+ }
+
+ public SessionPoolConfig setMaxSessionSize(int maxSessionSize) {
+ if (maxSessionSize < 1) {
+ throw new IllegalArgumentException("maxSessionSize cannot be less than 1.");
+ }
+ this.maxSessionSize = maxSessionSize;
+ return this;
+ }
+
+ public int getTimeout() {
+ return timeout;
+ }
+
+ public SessionPoolConfig setTimeout(int timeout) {
+ if (timeout < 0) {
+ throw new IllegalArgumentException("timeout cannot be less than 0.");
+ }
+ this.timeout = timeout;
+ return this;
+ }
+
+ public int getCleanTime() {
+ return cleanTime;
+ }
+
+ public SessionPoolConfig setCleanTime(int cleanTime) {
+ if (cleanTime < 0) {
+ throw new IllegalArgumentException("cleanTime cannot be less than 0.");
+ }
+ this.cleanTime = cleanTime;
+ return this;
+ }
+
+ public int getHealthCheckTime() {
+ return healthCheckTime;
+ }
+
+ public SessionPoolConfig setHealthCheckTime(int healthCheckTime) {
+ if (healthCheckTime < 0) {
+ throw new IllegalArgumentException("healthCheckTime cannot be less than 0.");
+ }
+ this.healthCheckTime = healthCheckTime;
+ return this;
+ }
+
+ public int getRetryConnectTimes() {
+ return retryConnectTimes;
+ }
+
+ public SessionPoolConfig setRetryConnectTimes(int retryConnectTimes) {
+ if (retryConnectTimes < 0) {
+ throw new IllegalArgumentException("retryConnectTimes cannot be less than 0.");
+ }
+ this.retryConnectTimes = retryConnectTimes;
+ return this;
+ }
+
+ public int getWaitTime() {
+ return waitTime;
+ }
+
+ public SessionPoolConfig setWaitTime(int waitTime) {
+ if (waitTime < 0) {
+ throw new IllegalArgumentException("waitTime cannot be less than 0.");
+ }
+ this.waitTime = waitTime;
+ return this;
+ }
+
+ public int getRetryTimes() {
+ return retryTimes;
+ }
+
+ public SessionPoolConfig setRetryTimes(int retryTimes) {
+ if (retryTimes < 0) {
+ throw new IllegalArgumentException("retryTimes cannot be less than 0.");
+ }
+ this.retryTimes = retryTimes;
+ return this;
+ }
+
+ public int getIntervalTime() {
+ return intervalTime;
+ }
+
+ public SessionPoolConfig setIntervalTime(int intervalTime) {
+ if (intervalTime < 0) {
+ throw new IllegalArgumentException("intervalTime cannot be less than 0.");
+ }
+ this.intervalTime = intervalTime;
+ return this;
+ }
+
+ public boolean isReconnect() {
+ return reconnect;
+ }
+
+ public SessionPoolConfig setReconnect(boolean reconnect) {
+ this.reconnect = reconnect;
+ return this;
+ }
+
+ public boolean isEnableSsl() {
+ return enableSsl;
+ }
+
+ public SessionPoolConfig setEnableSsl(boolean enableSsl) {
+ this.enableSsl = enableSsl;
+ return this;
+ }
+
+ public SSLParam getSslParam() {
+ return sslParam;
+ }
+
+ public SessionPoolConfig setSslParam(SSLParam sslParam) {
+ this.sslParam = sslParam;
+ return this;
+ }
+
+ public boolean isUseHttp2() {
+ return useHttp2;
+ }
+
+ public SessionPoolConfig setUseHttp2(boolean useHttp2) {
+ this.useHttp2 = useHttp2;
+ return this;
+ }
+
+ public Map getCustomHeaders() {
+ return customHeaders;
+ }
+
+ public SessionPoolConfig setCustomHeaders(Map customHeaders) {
+ this.customHeaders = customHeaders;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "SessionPoolConfig{"
+ + "username='" + username + '\''
+ + ", graphAddressList=" + graphAddressList
+ + ", spaceName='" + spaceName + '\''
+ + ", minSessionSize=" + minSessionSize
+ + ", maxSessionSize=" + maxSessionSize
+ + ", timeout=" + timeout
+ + ", cleanTime=" + cleanTime
+ + ", healthCheckTime=" + healthCheckTime
+ + ", retryTimes=" + retryTimes
+ + ", intervalTime=" + intervalTime
+ + ", reconnect=" + reconnect
+ + ", enableSsl=" + enableSsl
+ + ", sslParam=" + sslParam
+ + ", useHttp2=" + useHttp2
+ + ", customHeaders=" + customHeaders
+ + '}';
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/SessionsManagerConfig.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/SessionsManagerConfig.java
new file mode 100644
index 000000000..a8690ccc0
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/SessionsManagerConfig.java
@@ -0,0 +1,79 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph;
+
+import com.vesoft.nebula.driver.v3client.graph.data.HostAddress;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Configuration for {@code SessionsManager}, matching the v3 client.
+ */
+public class SessionsManagerConfig implements Serializable {
+
+ private static final long serialVersionUID = -2460063747630193912L;
+
+ private List addresses = new ArrayList<>();
+ private String userName = "root";
+ private String password = "nebula";
+ private String spaceName = "";
+ private Boolean reconnect = true;
+ private NebulaPoolConfig poolConfig = new NebulaPoolConfig();
+
+ public List getAddresses() {
+ return addresses;
+ }
+
+ public SessionsManagerConfig setAddresses(List addresses) {
+ this.addresses = addresses;
+ return this;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public SessionsManagerConfig setUserName(String userName) {
+ this.userName = userName;
+ return this;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public SessionsManagerConfig setPassword(String password) {
+ this.password = password;
+ return this;
+ }
+
+ public String getSpaceName() {
+ return spaceName;
+ }
+
+ public SessionsManagerConfig setSpaceName(String spaceName) {
+ this.spaceName = spaceName;
+ return this;
+ }
+
+ public Boolean getReconnect() {
+ return reconnect;
+ }
+
+ public void setReconnect(Boolean reconnect) {
+ this.reconnect = reconnect;
+ }
+
+ public NebulaPoolConfig getPoolConfig() {
+ return poolConfig;
+ }
+
+ public SessionsManagerConfig setPoolConfig(NebulaPoolConfig poolConfig) {
+ this.poolConfig = poolConfig;
+ return this;
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/BaseDataObject.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/BaseDataObject.java
new file mode 100644
index 000000000..5c3945c76
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/BaseDataObject.java
@@ -0,0 +1,35 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import java.io.Serializable;
+
+/**
+ * Base class for the wrapped graph data types, carrying the decode charset and timezone offset
+ * like the v3 client.
+ */
+public abstract class BaseDataObject implements Serializable {
+ private String decodeType = "utf-8";
+ private int timezoneOffset = 0;
+
+ public String getDecodeType() {
+ return decodeType;
+ }
+
+ public BaseDataObject setDecodeType(String decodeType) {
+ this.decodeType = decodeType;
+ return this;
+ }
+
+ public int getTimezoneOffset() {
+ return timezoneOffset;
+ }
+
+ public BaseDataObject setTimezoneOffset(int timezoneOffset) {
+ this.timezoneOffset = timezoneOffset;
+ return this;
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/CASignedSSLParam.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/CASignedSSLParam.java
new file mode 100644
index 000000000..aaac415e0
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/CASignedSSLParam.java
@@ -0,0 +1,38 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+/**
+ * CA-signed TLS configuration, matching the v3 client.
+ */
+public class CASignedSSLParam extends SSLParam {
+ private String caCrtFilePath;
+ private String crtFilePath;
+ private String keyFilePath;
+
+ public CASignedSSLParam() {
+ super(SignMode.CA_SIGNED);
+ }
+
+ public CASignedSSLParam(String caCrtFilePath, String crtFilePath, String keyFilePath) {
+ super(SignMode.CA_SIGNED);
+ this.caCrtFilePath = caCrtFilePath;
+ this.crtFilePath = crtFilePath;
+ this.keyFilePath = keyFilePath;
+ }
+
+ public String getCaCrtFilePath() {
+ return caCrtFilePath;
+ }
+
+ public String getCrtFilePath() {
+ return crtFilePath;
+ }
+
+ public String getKeyFilePath() {
+ return keyFilePath;
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/CoordinateWrapper.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/CoordinateWrapper.java
new file mode 100644
index 000000000..c4b692280
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/CoordinateWrapper.java
@@ -0,0 +1,50 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import com.vesoft.nebula.driver.graph.data.NPoint;
+import java.util.Objects;
+
+/**
+ * Wrapper for a geographic coordinate (a point), matching the v3 client.
+ */
+public class CoordinateWrapper extends BaseDataObject {
+ private final NPoint point;
+
+ public CoordinateWrapper(NPoint point) {
+ this.point = point;
+ }
+
+ public double getX() {
+ return point.getLng();
+ }
+
+ public double getY() {
+ return point.getLat();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CoordinateWrapper that = (CoordinateWrapper) o;
+ return this.getX() == that.getX() && this.getY() == that.getY();
+ }
+
+ @Override
+ public String toString() {
+ return "COORDINATE(" + point.getLng() + " " + point.getLat() + ")";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getX(), getY());
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/DateTimeWrapper.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/DateTimeWrapper.java
new file mode 100644
index 000000000..613033404
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/DateTimeWrapper.java
@@ -0,0 +1,144 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Objects;
+
+/**
+ * Wrapper for a datetime value, matching the v3 client.
+ */
+public class DateTimeWrapper extends BaseDataObject {
+ private final LocalDateTime utcDateTime;
+
+ public DateTimeWrapper(LocalDateTime localDateTime) {
+ this.utcDateTime = localDateTime;
+ }
+
+ public DateTimeWrapper(ZonedDateTime zonedDateTime) {
+ this.utcDateTime = zonedDateTime.withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
+ }
+
+ /**
+ * @return utc datetime year
+ */
+ public short getYear() {
+ return (short) utcDateTime.getYear();
+ }
+
+ /**
+ * @return utc datetime month
+ */
+ public byte getMonth() {
+ return (byte) utcDateTime.getMonthValue();
+ }
+
+ /**
+ * @return utc datetime day
+ */
+ public byte getDay() {
+ return (byte) utcDateTime.getDayOfMonth();
+ }
+
+ /**
+ * @return utc datetime hour
+ */
+ public byte getHour() {
+ return (byte) utcDateTime.getHour();
+ }
+
+ /**
+ * @return utc datetime minute
+ */
+ public byte getMinute() {
+ return (byte) utcDateTime.getMinute();
+ }
+
+ /**
+ * @return utc datetime second
+ */
+ public byte getSecond() {
+ return (byte) utcDateTime.getSecond();
+ }
+
+ /**
+ * @return utc datetime microsec
+ */
+ public int getMicrosec() {
+ return utcDateTime.getNano() / 1000;
+ }
+
+ /**
+ * @return the local datetime ({@link LocalDateTime}) after applying
+ * {@link #getTimezoneOffset()}.
+ */
+ public Object getLocalDateTime() {
+ return toLocalDateTime(getTimezoneOffset());
+ }
+
+ /**
+ * @return the datetime ({@link LocalDateTime}) with the specified timezone offset.
+ */
+ public Object getDateTimeWithTimezoneOffset(int timezoneOffset) {
+ return toLocalDateTime(timezoneOffset);
+ }
+
+ /**
+ * @return the local datetime string with the timezone offset applied.
+ */
+ public String getLocalDateTimeStr() {
+ return format(toLocalDateTime(getTimezoneOffset()));
+ }
+
+ /**
+ * @return the utc datetime string.
+ */
+ public String getUTCDateTimeStr() {
+ return format(utcDateTime);
+ }
+
+ private LocalDateTime toLocalDateTime(int timezoneOffset) {
+ if (timezoneOffset == 0) {
+ return utcDateTime;
+ }
+ return utcDateTime.atOffset(ZoneOffset.UTC)
+ .withOffsetSameInstant(ZoneOffset.ofTotalSeconds(timezoneOffset))
+ .toLocalDateTime();
+ }
+
+ private String format(LocalDateTime dateTime) {
+ return String.format("%d-%02d-%02dT%02d:%02d:%02d.%06d",
+ dateTime.getYear(), dateTime.getMonthValue(),
+ dateTime.getDayOfMonth(), dateTime.getHour(),
+ dateTime.getMinute(), dateTime.getSecond(),
+ dateTime.getNano() / 1000);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("utc datetime: %s, timezoneOffset: %d", getUTCDateTimeStr(),
+ getTimezoneOffset());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DateTimeWrapper that = (DateTimeWrapper) o;
+ return utcDateTime.equals(that.utcDateTime);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(utcDateTime);
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/DateWrapper.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/DateWrapper.java
new file mode 100644
index 000000000..03fe7d941
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/DateWrapper.java
@@ -0,0 +1,55 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import java.time.LocalDate;
+import java.util.Objects;
+
+/**
+ * Wrapper for a date value, matching the v3 client.
+ */
+public class DateWrapper extends BaseDataObject {
+ private final LocalDate date;
+
+ public DateWrapper(LocalDate date) {
+ this.date = date;
+ }
+
+ public short getYear() {
+ return (short) date.getYear();
+ }
+
+ public byte getMonth() {
+ return (byte) date.getMonthValue();
+ }
+
+ public byte getDay() {
+ return (byte) date.getDayOfMonth();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%d-%02d-%02d", date.getYear(), date.getMonthValue(),
+ date.getDayOfMonth());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DateWrapper that = (DateWrapper) o;
+ return date.equals(that.date);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(date);
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/DurationWrapper.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/DurationWrapper.java
new file mode 100644
index 000000000..5a7cf0cda
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/DurationWrapper.java
@@ -0,0 +1,82 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import com.vesoft.nebula.driver.graph.data.NDuration;
+import java.util.Objects;
+
+/**
+ * Wrapper for a duration value, matching the v3 client.
+ *
+ *
The v5 duration carries a richer field set (years/months/days/hours/minutes/seconds/
+ * microseconds); it is folded into the v3 three-field model ({@code months}/{@code seconds}/
+ * {@code microseconds}) on a best-effort basis.
+ */
+public class DurationWrapper extends BaseDataObject {
+ private final NDuration duration;
+
+ public DurationWrapper(NDuration duration) {
+ this.duration = duration;
+ }
+
+ /**
+ * @return the seconds part of the duration.
+ */
+ public long getSeconds() {
+ if (duration.isMonthBased()) {
+ return 0;
+ }
+ return duration.getDay() * 86400L + duration.getHour() * 3600L
+ + duration.getMinute() * 60L + duration.getSecond();
+ }
+
+ /**
+ * @return the microseconds part of the duration.
+ */
+ public int getMicroseconds() {
+ return duration.getMicrosecond();
+ }
+
+ /**
+ * @return the months part of the duration.
+ */
+ public int getMonths() {
+ if (!duration.isMonthBased()) {
+ return 0;
+ }
+ return duration.getYear() * 12 + duration.getMonth();
+ }
+
+ /**
+ * @return the duration string.
+ */
+ public String getDurationString() {
+ return String.format("duration({months:%d, seconds:%d, microseconds:%d})",
+ getMonths(), getSeconds(), getMicroseconds());
+ }
+
+ @Override
+ public String toString() {
+ return duration.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DurationWrapper that = (DurationWrapper) o;
+ return duration.equals(that.duration);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(duration);
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/GeographyWrapper.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/GeographyWrapper.java
new file mode 100644
index 000000000..2738465e1
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/GeographyWrapper.java
@@ -0,0 +1,65 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import com.vesoft.nebula.driver.graph.data.Geography;
+import com.vesoft.nebula.driver.graph.data.Geography.GeoShape;
+import java.util.Objects;
+
+/**
+ * Wrapper for a geography value, matching the v3 client.
+ */
+public class GeographyWrapper extends BaseDataObject {
+ private final Geography geography;
+
+ public GeographyWrapper(Geography geography) {
+ this.geography = geography;
+ }
+
+ public PolygonWrapper getPolygonWrapper() {
+ return new PolygonWrapper(geography.asPolygon());
+ }
+
+ public LineStringWrapper getLineStringWrapper() {
+ return new LineStringWrapper(geography.asLineString());
+ }
+
+ public PointWrapper getPointWrapper() {
+ return new PointWrapper(geography.asPoint());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ GeographyWrapper that = (GeographyWrapper) o;
+ return geography.toString().equals(that.geography.toString());
+ }
+
+ @Override
+ public String toString() {
+ GeoShape shape = geography.getShape();
+ switch (shape) {
+ case GeoShapePoint:
+ return getPointWrapper().toString();
+ case GeoShapeLineString:
+ return getLineStringWrapper().toString();
+ case GeoShapePolygon:
+ return getPolygonWrapper().toString();
+ default:
+ return "";
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(geography);
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/HostAddress.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/HostAddress.java
new file mode 100644
index 000000000..8c7ce7356
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/HostAddress.java
@@ -0,0 +1,71 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import java.io.Serializable;
+
+/**
+ * A graphd host address (host + port), matching the v3 client.
+ */
+public class HostAddress implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String host;
+ private final int port;
+
+ public HostAddress(String host, int port) {
+ this.host = host;
+ this.port = port;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ @Override
+ public int hashCode() {
+ return host.hashCode() + port;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof HostAddress) {
+ HostAddress that = (HostAddress) obj;
+ return this.host.equals(that.host) && this.port == that.port;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ if (host.contains(":")) {
+ return "[" + host + "]:" + port;
+ }
+ return host + ":" + port;
+ }
+
+ /**
+ * Convert to the v5 driver's host address type.
+ */
+ public com.vesoft.nebula.driver.graph.data.HostAddress toV5() {
+ return new com.vesoft.nebula.driver.graph.data.HostAddress(host, port);
+ }
+
+ /**
+ * Convert from the v5 driver's host address type.
+ */
+ public static HostAddress fromV5(com.vesoft.nebula.driver.graph.data.HostAddress address) {
+ return new HostAddress(address.getHost(), address.getPort());
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/LineStringWrapper.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/LineStringWrapper.java
new file mode 100644
index 000000000..4834edff1
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/LineStringWrapper.java
@@ -0,0 +1,64 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import com.vesoft.nebula.driver.graph.data.NLineString;
+import com.vesoft.nebula.driver.graph.data.NPoint;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Wrapper for a geographic line string, matching the v3 client.
+ */
+public class LineStringWrapper extends BaseDataObject {
+ private final NLineString lineString;
+
+ public LineStringWrapper(NLineString lineString) {
+ this.lineString = lineString;
+ }
+
+ public List getCoordinateList() {
+ List coordList = new ArrayList<>();
+ for (NPoint point : lineString.getPoints()) {
+ coordList.add(new CoordinateWrapper(point));
+ }
+ return coordList;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(lineString);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LineStringWrapper that = (LineStringWrapper) o;
+ return this.getCoordinateList().equals(that.getCoordinateList());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("LINESTRING(");
+ List points = lineString.getPoints();
+ for (int i = 0; i < points.size(); i++) {
+ NPoint point = points.get(i);
+ sb.append(point.getLng()).append(' ').append(point.getLat());
+ if (i < points.size() - 1) {
+ sb.append(',');
+ }
+ }
+ sb.append(')');
+ return sb.toString();
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/Node.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/Node.java
new file mode 100644
index 000000000..46a2c106f
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/Node.java
@@ -0,0 +1,124 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Wrapper around a graph node (vertex), matching the v3 client's {@code Node} API.
+ *
+ *
The v5 driver models a node as a single node type with a flat property map plus a list of
+ * labels, so the v3 per-tag accessors ({@link #values(String)}, {@link #keys(String)},
+ * {@link #properties(String)}) validate the given label and return the flat property set.
+ */
+public class Node extends BaseDataObject {
+
+ private final com.vesoft.nebula.driver.graph.data.Node node;
+
+ public Node(com.vesoft.nebula.driver.graph.data.Node node) {
+ if (node == null) {
+ throw new RuntimeException("Input an null node object");
+ }
+ this.node = node;
+ }
+
+ /**
+ * @return the node id as a {@link ValueWrapper}; call {@code getId().asLong()}.
+ */
+ public ValueWrapper getId() {
+ return ValueWrapper.ofLong(node.getId());
+ }
+
+ /**
+ * @return the node type name(s) exposed as labels.
+ */
+ public List tagNames() {
+ return new ArrayList<>(node.getLabels());
+ }
+
+ /**
+ * @return the labels of the node (alias of {@link #tagNames()}).
+ */
+ public List labels() {
+ return node.getLabels();
+ }
+
+ public boolean hasTagName(String tagName) {
+ return node.getLabels().contains(tagName);
+ }
+
+ public boolean hasLabel(String tagName) {
+ return node.getLabels().contains(tagName);
+ }
+
+ public List values(String tagName) {
+ checkTagName(tagName);
+ return new ArrayList<>(propertiesFor(tagName).values());
+ }
+
+ public List keys(String tagName) throws UnsupportedEncodingException {
+ checkTagName(tagName);
+ return new ArrayList<>(propertiesFor(tagName).keySet());
+ }
+
+ public HashMap properties(String tagName)
+ throws UnsupportedEncodingException {
+ checkTagName(tagName);
+ return propertiesFor(tagName);
+ }
+
+ private void checkTagName(String tagName) {
+ if (!node.getLabels().contains(tagName)) {
+ throw new IllegalArgumentException(tagName + " is not found");
+ }
+ }
+
+ private HashMap propertiesFor(String tagName) {
+ HashMap properties = new HashMap<>();
+ for (Map.Entry entry
+ : node.getProperties().entrySet()) {
+ properties.put(entry.getKey(), new ValueWrapper(entry.getValue(), getTimezoneOffset()));
+ }
+ return properties;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Node node = (Node) o;
+ return Objects.equals(this.node.getId(), node.node.getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(node.getId(), getDecodeType(), getTimezoneOffset());
+ }
+
+ @Override
+ public String toString() {
+ List tagsStr = new ArrayList<>();
+ Map props = node.getProperties();
+ List propStrs = new ArrayList<>();
+ for (Map.Entry entry
+ : props.entrySet()) {
+ propStrs.add(entry.getKey() + ": " + entry.getValue().toString());
+ }
+ for (String name : node.getLabels()) {
+ tagsStr.add(String.format(":%s {%s}", name, String.join(", ", propStrs)));
+ }
+ return String.format("(%d %s)", node.getId(), String.join(" ", tagsStr));
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/PathWrapper.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/PathWrapper.java
new file mode 100644
index 000000000..0590121eb
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/PathWrapper.java
@@ -0,0 +1,167 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import com.vesoft.nebula.driver.v3client.graph.exception.InvalidValueException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Wrapper around a graph path, matching the v3 client's {@code PathWrapper} API.
+ */
+public class PathWrapper extends BaseDataObject {
+
+ public static class Segment {
+ Node startNode;
+ Relationship relationShip;
+ Node endNode;
+
+ public Segment(Node startNode, Relationship relationShip, Node endNode) {
+ this.startNode = startNode;
+ this.relationShip = relationShip;
+ this.endNode = endNode;
+ }
+
+ public Node getStartNode() {
+ return startNode;
+ }
+
+ public Relationship getRelationShip() {
+ return relationShip;
+ }
+
+ public Node getEndNode() {
+ return endNode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Segment segment = (Segment) o;
+ return Objects.equals(startNode, segment.startNode)
+ && Objects.equals(relationShip, segment.relationShip)
+ && Objects.equals(endNode, segment.endNode);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(startNode, relationShip, endNode);
+ }
+
+ @Override
+ public String toString() {
+ return "Segment{" + "startNode=" + startNode + ", relationShip=" + relationShip
+ + ", endNode=" + endNode + '}';
+ }
+ }
+
+ private final List segments = new ArrayList<>();
+ private final List nodes = new ArrayList<>();
+ private final List relationships = new ArrayList<>();
+
+ public PathWrapper(com.vesoft.nebula.driver.graph.data.Path path, int timezoneOffset)
+ throws InvalidValueException {
+ setTimezoneOffset(timezoneOffset);
+ if (path == null) {
+ return;
+ }
+ for (com.vesoft.nebula.driver.graph.data.Node node : path.nodes()) {
+ nodes.add((Node) new Node(node).setTimezoneOffset(timezoneOffset));
+ }
+ for (com.vesoft.nebula.driver.graph.data.Edge edge : path.edges()) {
+ relationships.add((Relationship) new Relationship(edge).setTimezoneOffset(
+ timezoneOffset));
+ }
+ for (int i = 0; i < relationships.size(); i++) {
+ if (i + 1 >= nodes.size()) {
+ throw new InvalidValueException("Malformed path: not enough nodes for edges");
+ }
+ segments.add(new Segment(nodes.get(i), relationships.get(i), nodes.get(i + 1)));
+ }
+ }
+
+ public Node getStartNode() {
+ if (nodes == null || nodes.isEmpty()) {
+ return null;
+ }
+ return nodes.get(0);
+ }
+
+ public Node getEndNode() {
+ if (nodes == null || nodes.isEmpty()) {
+ return null;
+ }
+ return nodes.get(nodes.size() - 1);
+ }
+
+ public boolean containNode(Node node) {
+ return nodes.contains(node);
+ }
+
+ public boolean containRelationship(Relationship relationship) {
+ return relationships.contains(relationship);
+ }
+
+ public List getNodes() {
+ return nodes;
+ }
+
+ public List getRelationships() {
+ return relationships;
+ }
+
+ public List getSegments() {
+ return segments;
+ }
+
+ public int length() {
+ return segments.size();
+ }
+
+ @Override
+ public String toString() {
+ if (nodes.isEmpty()) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(nodes.get(0).toString());
+ for (int i = 0; i < relationships.size(); i++) {
+ Relationship relationship = relationships.get(i);
+ sb.append("-[:").append(relationship.edgeName()).append('@')
+ .append(relationship.ranking()).append("{}]->");
+ if (i + 1 < nodes.size()) {
+ sb.append(nodes.get(i + 1).toString());
+ }
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PathWrapper that = (PathWrapper) o;
+ return Objects.equals(segments, that.segments)
+ && Objects.equals(nodes, that.nodes)
+ && Objects.equals(relationships, that.relationships);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(segments, nodes, relationships);
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/PointWrapper.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/PointWrapper.java
new file mode 100644
index 000000000..1d654fc17
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/PointWrapper.java
@@ -0,0 +1,46 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import com.vesoft.nebula.driver.graph.data.NPoint;
+import java.util.Objects;
+
+/**
+ * Wrapper for a geographic point, matching the v3 client.
+ */
+public class PointWrapper extends BaseDataObject {
+ private final NPoint point;
+
+ public PointWrapper(NPoint point) {
+ this.point = point;
+ }
+
+ public CoordinateWrapper getCoordinate() {
+ return new CoordinateWrapper(point);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PointWrapper that = (PointWrapper) o;
+ return this.getCoordinate().equals(that.getCoordinate());
+ }
+
+ @Override
+ public String toString() {
+ return "POINT(" + point.getLng() + " " + point.getLat() + ")";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(point.getLng(), point.getLat());
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/PolygonWrapper.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/PolygonWrapper.java
new file mode 100644
index 000000000..3cb604f60
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/PolygonWrapper.java
@@ -0,0 +1,76 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import com.vesoft.nebula.driver.graph.data.NPoint;
+import com.vesoft.nebula.driver.graph.data.NPolygon;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Wrapper for a geographic polygon, matching the v3 client.
+ */
+public class PolygonWrapper extends BaseDataObject {
+ private final NPolygon polygon;
+
+ public PolygonWrapper(NPolygon polygon) {
+ this.polygon = polygon;
+ }
+
+ public List> getCoordListList() {
+ List> coordListList = new ArrayList<>();
+ for (List loop : polygon.getLoops()) {
+ List coordList = new ArrayList<>();
+ for (NPoint point : loop) {
+ coordList.add(new CoordinateWrapper(point));
+ }
+ coordListList.add(coordList);
+ }
+ return coordListList;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(polygon);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PolygonWrapper that = (PolygonWrapper) o;
+ return this.getCoordListList().equals(that.getCoordListList());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("POLYGON(");
+ List> loops = polygon.getLoops();
+ for (int i = 0; i < loops.size(); i++) {
+ sb.append('(');
+ List loop = loops.get(i);
+ for (int j = 0; j < loop.size(); j++) {
+ NPoint point = loop.get(j);
+ sb.append(point.getLng()).append(' ').append(point.getLat());
+ if (j < loop.size() - 1) {
+ sb.append(',');
+ }
+ }
+ sb.append(')');
+ if (i < loops.size() - 1) {
+ sb.append(',');
+ }
+ }
+ sb.append(')');
+ return sb.toString();
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/Relationship.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/Relationship.java
new file mode 100644
index 000000000..99320c8e2
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/Relationship.java
@@ -0,0 +1,114 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Wrapper around a graph relationship (edge), matching the v3 client's {@code Relationship} API.
+ */
+public class Relationship extends BaseDataObject {
+
+ private final com.vesoft.nebula.driver.graph.data.Edge edge;
+
+ public Relationship(com.vesoft.nebula.driver.graph.data.Edge edge) {
+ if (edge == null) {
+ throw new RuntimeException("Input an null edge object");
+ }
+ this.edge = edge;
+ }
+
+ /**
+ * @return the source id as a {@link ValueWrapper}.
+ */
+ public ValueWrapper srcId() {
+ return ValueWrapper.ofLong(edge.getSrcId());
+ }
+
+ /**
+ * @return the destination id as a {@link ValueWrapper}.
+ */
+ public ValueWrapper dstId() {
+ return ValueWrapper.ofLong(edge.getDstId());
+ }
+
+ /**
+ * @return the edge name. The v3 client used the edge label as the name; the v5 driver stores
+ * the label(s) separately from the edge type name, so prefer the first label.
+ */
+ public String edgeName() {
+ if (edge.getLabels() != null && !edge.getLabels().isEmpty()) {
+ return edge.getLabels().get(0);
+ }
+ return edge.getType();
+ }
+
+ /**
+ * @return the rank of the edge.
+ */
+ public long ranking() {
+ return edge.getRank();
+ }
+
+ public List keys() throws UnsupportedEncodingException {
+ return new ArrayList<>(edge.getColumnNames());
+ }
+
+ public List values() {
+ List propVals = new ArrayList<>();
+ for (com.vesoft.nebula.driver.graph.data.ValueWrapper val : edge.getPropertyValues()) {
+ propVals.add(new ValueWrapper(val, getTimezoneOffset()));
+ }
+ return propVals;
+ }
+
+ public HashMap properties() throws UnsupportedEncodingException {
+ HashMap properties = new HashMap<>();
+ for (Map.Entry entry
+ : edge.getProperties().entrySet()) {
+ properties.put(entry.getKey(), new ValueWrapper(entry.getValue(), getTimezoneOffset()));
+ }
+ return properties;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Relationship that = (Relationship) o;
+ return edge.getRank() == that.edge.getRank()
+ && edge.getSrcId() == that.edge.getSrcId()
+ && edge.getDstId() == that.edge.getDstId()
+ && Objects.equals(edge.getType(), that.edge.getType());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(edge.getType(), edge.getRank(), edge.getSrcId(), edge.getDstId(),
+ getDecodeType(), getTimezoneOffset());
+ }
+
+ @Override
+ public String toString() {
+ List propStrs = new ArrayList<>();
+ for (Map.Entry entry
+ : edge.getProperties().entrySet()) {
+ propStrs.add(entry.getKey() + ": " + entry.getValue().toString());
+ }
+ return String.format("(%d)-[:%s@%d{%s}]->(%d)",
+ edge.getSrcId(), edge.getType(), edge.getRank(),
+ String.join(", ", propStrs), edge.getDstId());
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/ResultSet.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/ResultSet.java
new file mode 100644
index 000000000..d25ca27ef
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/ResultSet.java
@@ -0,0 +1,209 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import com.vesoft.nebula.driver.v3client.graph.ErrorCode;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+
+/**
+ * The result of a query, matching the v3 client's {@code ResultSet} API.
+ *
+ *
The v5 driver returns a one-shot forward iterator; this wrapper eagerly materializes all rows
+ * so that the v3 index-based accessors ({@link #rowValues(int)}, {@link #colValues(String)}) and
+ * repeated iteration keep working.
+ */
+public class ResultSet {
+
+ public static class Record implements Iterable {
+
+ private final List colValues = new ArrayList<>();
+ private final List columnNames;
+
+ public Record(List columnNames,
+ com.vesoft.nebula.driver.graph.data.ResultSet.Record record,
+ int timezoneOffset) {
+ this.columnNames = columnNames;
+ if (record == null) {
+ return;
+ }
+ for (com.vesoft.nebula.driver.graph.data.ValueWrapper value : record.values()) {
+ this.colValues.add(new ValueWrapper(value, timezoneOffset));
+ }
+ }
+
+ @Override
+ public Iterator iterator() {
+ return this.colValues.iterator();
+ }
+
+ @Override
+ public void forEach(Consumer super ValueWrapper> action) {
+ this.colValues.forEach(action);
+ }
+
+ @Override
+ public Spliterator spliterator() {
+ return this.colValues.spliterator();
+ }
+
+ @Override
+ public String toString() {
+ List valueStr = new ArrayList<>();
+ for (ValueWrapper v : colValues) {
+ valueStr.add(v.toString());
+ }
+ return String.format("ColumnName: %s, Values: %s",
+ columnNames.toString(), valueStr.toString());
+ }
+
+ public ValueWrapper get(int index) {
+ if (index >= columnNames.size()) {
+ throw new IllegalArgumentException(
+ String.format("Cannot get field because the key '%d' out of range", index));
+ }
+ return this.colValues.get(index);
+ }
+
+ public ValueWrapper get(String columnName) {
+ int index = columnNames.indexOf(columnName);
+ if (index == -1) {
+ throw new IllegalArgumentException(
+ "Cannot get field because the columnName '"
+ + columnName + "' is not exists");
+ }
+ return this.colValues.get(index);
+ }
+
+ public List values() {
+ return colValues;
+ }
+
+ public int size() {
+ return this.columnNames.size();
+ }
+
+ public boolean contains(String columnName) {
+ return this.columnNames.contains(columnName);
+ }
+ }
+
+ private final List columnNames = new ArrayList<>();
+ private final List records = new ArrayList<>();
+ private final int timezoneOffset;
+ private final boolean succeeded;
+ private final boolean empty;
+ private final int errorCode;
+ private final String errorMessage;
+ private final long latency;
+ private final com.vesoft.nebula.driver.graph.data.PlanInfoNode planDesc;
+
+ public ResultSet(com.vesoft.nebula.driver.graph.data.ResultSet resultSet) {
+ this(resultSet, 0);
+ }
+
+ public ResultSet(com.vesoft.nebula.driver.graph.data.ResultSet resultSet, int timezoneOffset) {
+ if (resultSet == null) {
+ throw new RuntimeException("Input an null `ResultSet' object");
+ }
+ this.timezoneOffset = timezoneOffset;
+ this.succeeded = resultSet.isSucceeded();
+ this.empty = resultSet.isEmpty();
+ this.errorCode = ErrorCode.fromV5ErrorCode(resultSet.getErrorCode());
+ this.errorMessage = resultSet.getErrorMessage();
+ this.latency = resultSet.getLatency();
+ this.planDesc = resultSet.getPlanDesc();
+ this.columnNames.addAll(resultSet.getColumnNames());
+ while (resultSet.hasNext()) {
+ records.add(new Record(columnNames, resultSet.next(), timezoneOffset));
+ }
+ }
+
+ public boolean isSucceeded() {
+ return succeeded;
+ }
+
+ public boolean isEmpty() {
+ return empty;
+ }
+
+ public int getErrorCode() {
+ return errorCode;
+ }
+
+ public String getSpaceName() {
+ return "";
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public String getComment() {
+ return "";
+ }
+
+ public long getLatency() {
+ return latency;
+ }
+
+ public com.vesoft.nebula.driver.graph.data.PlanInfoNode getPlanDesc() {
+ return planDesc;
+ }
+
+ public List keys() {
+ return columnNames;
+ }
+
+ public List getColumnNames() {
+ return columnNames;
+ }
+
+ public int rowsSize() {
+ return records.size();
+ }
+
+ public Record rowValues(int index) {
+ if (index >= records.size()) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ return records.get(index);
+ }
+
+ public List colValues(String columnName) {
+ int index = columnNames.indexOf(columnName);
+ if (index < 0) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ List values = new ArrayList<>();
+ for (Record record : records) {
+ values.add(record.get(index));
+ }
+ return values;
+ }
+
+ @Override
+ public String toString() {
+ if (!isSucceeded()) {
+ return getErrorMessage();
+ }
+ int i = 0;
+ List rowStrs = new ArrayList<>();
+ while (i < rowsSize()) {
+ List valueStrs = new ArrayList<>();
+ for (ValueWrapper value : rowValues(i)) {
+ valueStrs.add(value.toString());
+ }
+ rowStrs.add(String.join(",", valueStrs));
+ i++;
+ }
+ return String.format("ColumnName: %s, Rows: %s",
+ columnNames.toString(), rowStrs.toString());
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/SSLParam.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/SSLParam.java
new file mode 100644
index 000000000..d3e8da566
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/SSLParam.java
@@ -0,0 +1,42 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import java.io.Serializable;
+
+/**
+ * Base class for TLS configuration, matching the v3 client.
+ */
+public abstract class SSLParam implements Serializable {
+
+ private static final long serialVersionUID = 7410233298826490747L;
+
+ private boolean skipVerifyServer = false;
+
+ public enum SignMode {
+ NONE,
+ SELF_SIGNED,
+ CA_SIGNED
+ }
+
+ private SignMode signMode;
+
+ public boolean isSkipVerifyServer() {
+ return skipVerifyServer;
+ }
+
+ public void setSkipVerifyServer(boolean skipVerifyServer) {
+ this.skipVerifyServer = skipVerifyServer;
+ }
+
+ public SSLParam(SignMode signMode) {
+ this.signMode = signMode;
+ }
+
+ public SignMode getSignMode() {
+ return signMode;
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/SelfSignedSSLParam.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/SelfSignedSSLParam.java
new file mode 100644
index 000000000..d3d977c91
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/SelfSignedSSLParam.java
@@ -0,0 +1,34 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+/**
+ * Self-signed TLS configuration, matching the v3 client.
+ */
+public class SelfSignedSSLParam extends SSLParam {
+ private String crtFilePath;
+ private String keyFilePath;
+ private String password;
+
+ public SelfSignedSSLParam(String crtFilePath, String keyFilePath, String password) {
+ super(SignMode.SELF_SIGNED);
+ this.crtFilePath = crtFilePath;
+ this.keyFilePath = keyFilePath;
+ this.password = password;
+ }
+
+ public String getCrtFilePath() {
+ return crtFilePath;
+ }
+
+ public String getKeyFilePath() {
+ return keyFilePath;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/TimeUtil.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/TimeUtil.java
new file mode 100644
index 000000000..87b81b1be
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/TimeUtil.java
@@ -0,0 +1,48 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneOffset;
+
+/**
+ * Timezone conversion helpers used by the compatibility wrappers.
+ *
+ *
The v3 client operated on Thrift time structs; the compatibility layer operates directly on
+ * {@code java.time} types, so these helpers have matching names but java.time signatures.
+ */
+public class TimeUtil {
+
+ /**
+ * @param utcDateTime the utc datetime
+ * @param timezoneOffset the timezone offset, unit is seconds
+ * @return the datetime shifted to the timezone offset
+ */
+ public static LocalDateTime datetimeConvertWithTimezone(LocalDateTime utcDateTime,
+ int timezoneOffset) {
+ if (timezoneOffset == 0) {
+ return utcDateTime;
+ }
+ return utcDateTime.atOffset(ZoneOffset.UTC)
+ .withOffsetSameInstant(ZoneOffset.ofTotalSeconds(timezoneOffset))
+ .toLocalDateTime();
+ }
+
+ /**
+ * @param utcTime the utc time
+ * @param timezoneOffset the timezone offset, unit is seconds
+ * @return the time shifted to the timezone offset
+ */
+ public static LocalTime timeConvertWithTimezone(LocalTime utcTime, int timezoneOffset) {
+ if (timezoneOffset == 0) {
+ return utcTime;
+ }
+ return utcTime.atOffset(ZoneOffset.UTC)
+ .withOffsetSameInstant(ZoneOffset.ofTotalSeconds(timezoneOffset))
+ .toLocalTime();
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/TimeWrapper.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/TimeWrapper.java
new file mode 100644
index 000000000..1a2e806bb
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/TimeWrapper.java
@@ -0,0 +1,123 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import java.time.LocalTime;
+import java.time.OffsetTime;
+import java.time.ZoneOffset;
+import java.util.Objects;
+
+/**
+ * Wrapper for a time value, matching the v3 client.
+ *
+ *
The stored time is normalized to UTC; the local-time helpers apply the
+ * {@link #getTimezoneOffset() timezone offset} inherited from {@link BaseDataObject}.
+ */
+public class TimeWrapper extends BaseDataObject {
+ private final LocalTime utcTime;
+
+ public TimeWrapper(LocalTime localTime) {
+ this.utcTime = localTime;
+ }
+
+ public TimeWrapper(OffsetTime offsetTime) {
+ this.utcTime = offsetTime.withOffsetSameInstant(ZoneOffset.UTC).toLocalTime();
+ }
+
+ /**
+ * @return utc Time hour
+ */
+ public byte getHour() {
+ return (byte) utcTime.getHour();
+ }
+
+ /**
+ * @return utc Time minute
+ */
+ public byte getMinute() {
+ return (byte) utcTime.getMinute();
+ }
+
+ /**
+ * @return utc Time second
+ */
+ public byte getSecond() {
+ return (byte) utcTime.getSecond();
+ }
+
+ /**
+ * @return utc Time microsec
+ */
+ public int getMicrosec() {
+ return utcTime.getNano() / 1000;
+ }
+
+ /**
+ * @return the local time ({@link LocalTime}) after applying {@link #getTimezoneOffset()}.
+ */
+ public Object getLocalTime() {
+ return toLocalTime(getTimezoneOffset());
+ }
+
+ /**
+ * @return the time ({@link LocalTime}) with the specified timezone offset.
+ */
+ public Object getTimeWithTimezoneOffset(int timezoneOffset) {
+ return toLocalTime(timezoneOffset);
+ }
+
+ /**
+ * @return the local time string with the timezone offset applied.
+ */
+ public String getLocalTimeStr() {
+ return format(toLocalTime(getTimezoneOffset()));
+ }
+
+ /**
+ * @return the utc time string.
+ */
+ public String getUTCTimeStr() {
+ return format(utcTime);
+ }
+
+ private LocalTime toLocalTime(int timezoneOffset) {
+ if (timezoneOffset == 0) {
+ return utcTime;
+ }
+ return utcTime.atOffset(ZoneOffset.UTC)
+ .withOffsetSameInstant(ZoneOffset.ofTotalSeconds(timezoneOffset))
+ .toLocalTime();
+ }
+
+ private String format(LocalTime time) {
+ return String.format("%02d:%02d:%02d.%06d",
+ time.getHour(), time.getMinute(), time.getSecond(),
+ time.getNano() / 1000);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("utc time: %s, timezoneOffset: %d", getUTCTimeStr(),
+ getTimezoneOffset());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeWrapper that = (TimeWrapper) o;
+ return utcTime.equals(that.utcTime);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(utcTime);
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/ValueWrapper.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/ValueWrapper.java
new file mode 100644
index 000000000..535a62086
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/data/ValueWrapper.java
@@ -0,0 +1,396 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.data;
+
+import com.vesoft.nebula.driver.graph.decode.ColumnType;
+import com.vesoft.nebula.driver.v3client.graph.exception.InvalidValueException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Wrapper around a NebulaGraph value, matching the v3 client's {@code ValueWrapper} API.
+ *
+ *
Internally it wraps a v5 driver {@link com.vesoft.nebula.driver.graph.data.ValueWrapper}.
+ */
+public class ValueWrapper {
+
+ public static class NullType {
+ public static final int __NULL__ = 0;
+ public static final int NaN = 1;
+ public static final int BAD_DATA = 2;
+ public static final int BAD_TYPE = 3;
+ public static final int ERR_OVERFLOW = 4;
+ public static final int UNKNOWN_PROP = 5;
+ public static final int DIV_BY_ZERO = 6;
+ public static final int OUT_OF_RANGE = 7;
+ int nullType;
+
+ public NullType(int nullType) {
+ this.nullType = nullType;
+ }
+
+ public int getNullType() {
+ return nullType;
+ }
+
+ @Override
+ public String toString() {
+ switch (nullType) {
+ case __NULL__:
+ return "NULL";
+ case NaN:
+ return "NaN";
+ case BAD_DATA:
+ return "BAD_DATA";
+ case BAD_TYPE:
+ return "BAD_TYPE";
+ case ERR_OVERFLOW:
+ return "ERR_OVERFLOW";
+ case UNKNOWN_PROP:
+ return "UNKNOWN_PROP";
+ case DIV_BY_ZERO:
+ return "DIV_BY_ZERO";
+ case OUT_OF_RANGE:
+ return "OUT_OF_RANGE";
+ default:
+ return "Unknown type: " + nullType;
+ }
+ }
+ }
+
+ private final com.vesoft.nebula.driver.graph.data.ValueWrapper value;
+ private final int timezoneOffset;
+
+ public ValueWrapper(com.vesoft.nebula.driver.graph.data.ValueWrapper value) {
+ this(value, 0);
+ }
+
+ public ValueWrapper(com.vesoft.nebula.driver.graph.data.ValueWrapper value,
+ int timezoneOffset) {
+ this.value = value;
+ this.timezoneOffset = timezoneOffset;
+ }
+
+ /**
+ * Build a compatibility value wrapping a {@code long} (used for node/edge ids).
+ */
+ public static ValueWrapper ofLong(long value) {
+ return new ValueWrapper(new com.vesoft.nebula.driver.graph.data.ValueWrapper(
+ value, ColumnType.COLUMN_TYPE_INT64));
+ }
+
+ /**
+ * @return the underlying v5 value wrapper.
+ */
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * The v5 driver has no distinct "empty" type; always returns {@code false}.
+ */
+ public boolean isEmpty() {
+ return false;
+ }
+
+ public boolean isNull() {
+ return value.isNull();
+ }
+
+ public boolean isBoolean() {
+ return value.isBoolean();
+ }
+
+ public boolean isLong() {
+ return value.isLong() || value.isInt();
+ }
+
+ public boolean isDouble() {
+ return value.isDouble() || value.isFloat() || value.isDecimal();
+ }
+
+ public boolean isString() {
+ return value.isString();
+ }
+
+ public boolean isList() {
+ return value.isList();
+ }
+
+ public boolean isSet() {
+ return value.isSet();
+ }
+
+ public boolean isMap() {
+ return value.isMap();
+ }
+
+ public boolean isTime() {
+ return value.isLocalTime() || value.isZonedTime();
+ }
+
+ public boolean isDate() {
+ return value.isDate();
+ }
+
+ public boolean isDateTime() {
+ return value.isLocalDateTime() || value.isZonedDateTime();
+ }
+
+ public boolean isVertex() {
+ return value.isNode();
+ }
+
+ public boolean isEdge() {
+ return value.isEdge();
+ }
+
+ public boolean isPath() {
+ return value.isPath();
+ }
+
+ public boolean isGeography() {
+ return value.isGeography();
+ }
+
+ public boolean isDuration() {
+ return value.isDuration();
+ }
+
+ public NullType asNull() throws InvalidValueException {
+ if (value.isNull()) {
+ return new NullType(NullType.__NULL__);
+ }
+ throw new InvalidValueException(
+ "Cannot get field nullType because value's type is " + value.getDataTypeString());
+ }
+
+ public boolean asBoolean() throws InvalidValueException {
+ if (value.isBoolean()) {
+ return value.asBoolean();
+ }
+ throw new InvalidValueException(
+ "Cannot get field boolean because value's type is " + value.getDataTypeString());
+ }
+
+ public long asLong() throws InvalidValueException {
+ if (value.isLong()) {
+ return value.asLong();
+ }
+ if (value.isInt()) {
+ return value.asInt();
+ }
+ throw new InvalidValueException(
+ "Cannot get field long because value's type is " + value.getDataTypeString());
+ }
+
+ public String asString() throws InvalidValueException, UnsupportedEncodingException {
+ if (value.isString()) {
+ return value.asString();
+ }
+ throw new InvalidValueException(
+ "Cannot get field string because value's type is " + value.getDataTypeString());
+ }
+
+ public double asDouble() throws InvalidValueException {
+ if (value.isDouble()) {
+ return value.asDouble();
+ }
+ if (value.isFloat()) {
+ return value.asFloat();
+ }
+ if (value.isDecimal()) {
+ return value.asDecimal().doubleValue();
+ }
+ throw new InvalidValueException(
+ "Cannot get field double because value's type is " + value.getDataTypeString());
+ }
+
+ public ArrayList asList() throws InvalidValueException {
+ if (value.isList()) {
+ ArrayList values = new ArrayList<>();
+ for (com.vesoft.nebula.driver.graph.data.ValueWrapper element : value.asList()) {
+ values.add(new ValueWrapper(element, timezoneOffset));
+ }
+ return values;
+ }
+ throw new InvalidValueException(
+ "Cannot get field `list' because value's type is " + value.getDataTypeString());
+ }
+
+ public HashSet asSet() throws InvalidValueException {
+ if (value.isSet()) {
+ HashSet values = new HashSet<>();
+ Set> set = value.asSet();
+ for (Object element : set) {
+ values.add(new ValueWrapper(
+ (com.vesoft.nebula.driver.graph.data.ValueWrapper) element, timezoneOffset));
+ }
+ return values;
+ }
+ throw new InvalidValueException(
+ "Cannot get field `set' because value's type is " + value.getDataTypeString());
+ }
+
+ public HashMap asMap()
+ throws InvalidValueException, UnsupportedEncodingException {
+ if (value.isMap()) {
+ HashMap kvs = new HashMap<>();
+ Map, ?> map = value.asMap();
+ for (Map.Entry, ?> entry : map.entrySet()) {
+ com.vesoft.nebula.driver.graph.data.ValueWrapper key =
+ (com.vesoft.nebula.driver.graph.data.ValueWrapper) entry.getKey();
+ com.vesoft.nebula.driver.graph.data.ValueWrapper val =
+ (com.vesoft.nebula.driver.graph.data.ValueWrapper) entry.getValue();
+ kvs.put(key.toString(), new ValueWrapper(val, timezoneOffset));
+ }
+ return kvs;
+ }
+ throw new InvalidValueException(
+ "Cannot get field `map' because value's type is " + value.getDataTypeString());
+ }
+
+ public TimeWrapper asTime() throws InvalidValueException {
+ TimeWrapper wrapper;
+ if (value.isLocalTime()) {
+ wrapper = new TimeWrapper(value.asLocalTime());
+ } else if (value.isZonedTime()) {
+ wrapper = new TimeWrapper(value.asZonedTime());
+ } else {
+ throw new InvalidValueException(
+ "Cannot get field time because value's type is " + value.getDataTypeString());
+ }
+ return (TimeWrapper) wrapper.setTimezoneOffset(timezoneOffset);
+ }
+
+ public DateWrapper asDate() throws InvalidValueException {
+ if (value.isDate()) {
+ return (DateWrapper) new DateWrapper(value.asDate()).setTimezoneOffset(timezoneOffset);
+ }
+ throw new InvalidValueException(
+ "Cannot get field date because value's type is " + value.getDataTypeString());
+ }
+
+ public DateTimeWrapper asDateTime() throws InvalidValueException {
+ DateTimeWrapper wrapper;
+ if (value.isLocalDateTime()) {
+ wrapper = new DateTimeWrapper(value.asLocalDateTime());
+ } else if (value.isZonedDateTime()) {
+ wrapper = new DateTimeWrapper(value.asZonedDateTime());
+ } else {
+ throw new InvalidValueException(
+ "Cannot get field datetime because value's type is " + value.getDataTypeString());
+ }
+ return (DateTimeWrapper) wrapper.setTimezoneOffset(timezoneOffset);
+ }
+
+ public Node asNode() throws InvalidValueException, UnsupportedEncodingException {
+ if (value.isNode()) {
+ return (Node) new Node(value.asNode()).setTimezoneOffset(timezoneOffset);
+ }
+ throw new InvalidValueException(
+ "Cannot get field Node because value's type is " + value.getDataTypeString());
+ }
+
+ public Relationship asRelationship() throws InvalidValueException {
+ if (value.isEdge()) {
+ return (Relationship) new Relationship(value.asEdge()).setTimezoneOffset(
+ timezoneOffset);
+ }
+ throw new InvalidValueException(
+ "Cannot get field Relationship because value's type is " + value.getDataTypeString());
+ }
+
+ public PathWrapper asPath() throws InvalidValueException, UnsupportedEncodingException {
+ if (value.isPath()) {
+ return new PathWrapper(value.asPath(), timezoneOffset);
+ }
+ throw new InvalidValueException(
+ "Cannot get field PathWrapper because value's type is " + value.getDataTypeString());
+ }
+
+ public GeographyWrapper asGeography() throws InvalidValueException {
+ if (value.isGeography()) {
+ return (GeographyWrapper) new GeographyWrapper(value.asGeography())
+ .setTimezoneOffset(timezoneOffset);
+ }
+ throw new InvalidValueException(
+ "Cannot get field GeographyWrapper because value's type is " + value.getDataTypeString());
+ }
+
+ public DurationWrapper asDuration() throws InvalidValueException {
+ if (value.isDuration()) {
+ return (DurationWrapper) new DurationWrapper(value.asDuration())
+ .setTimezoneOffset(timezoneOffset);
+ }
+ throw new InvalidValueException(
+ "Cannot get field DurationWrapper because value's type is " + value.getDataTypeString());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ValueWrapper that = (ValueWrapper) o;
+ return Objects.equals(value, that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+
+ @Override
+ public String toString() {
+ try {
+ if (isNull()) {
+ return asNull().toString();
+ } else if (isBoolean()) {
+ return String.valueOf(asBoolean());
+ } else if (isLong()) {
+ return String.valueOf(asLong());
+ } else if (isDouble()) {
+ return String.valueOf(asDouble());
+ } else if (isString()) {
+ return "\"" + asString() + "\"";
+ } else if (isList()) {
+ return asList().toString();
+ } else if (isSet()) {
+ return asSet().toString();
+ } else if (isMap()) {
+ return asMap().toString();
+ } else if (isTime()) {
+ return asTime().toString();
+ } else if (isDate()) {
+ return asDate().toString();
+ } else if (isDateTime()) {
+ return asDateTime().toString();
+ } else if (isVertex()) {
+ return asNode().toString();
+ } else if (isEdge()) {
+ return asRelationship().toString();
+ } else if (isPath()) {
+ return asPath().toString();
+ } else if (isGeography()) {
+ return asGeography().toString();
+ } else if (isDuration()) {
+ return asDuration().toString();
+ }
+ return "Unknown type: " + value.getDataTypeString();
+ } catch (Exception e) {
+ return e.getMessage();
+ }
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/AuthFailedException.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/AuthFailedException.java
new file mode 100644
index 000000000..43a47afc9
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/AuthFailedException.java
@@ -0,0 +1,15 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.exception;
+
+/**
+ * Thrown when user authentication against the NebulaGraph server fails.
+ */
+public class AuthFailedException extends Exception {
+ public AuthFailedException(String message) {
+ super(String.format("Auth failed: %s", message));
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/BindSpaceFailedException.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/BindSpaceFailedException.java
new file mode 100644
index 000000000..724145324
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/BindSpaceFailedException.java
@@ -0,0 +1,17 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.exception;
+
+/**
+ * Thrown when binding the working graph (space) fails.
+ */
+public class BindSpaceFailedException extends Exception {
+ private static final long serialVersionUID = -8678623814979666625L;
+
+ public BindSpaceFailedException(String message) {
+ super(String.format("use space failed: %s", message));
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/ClientServerIncompatibleException.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/ClientServerIncompatibleException.java
new file mode 100644
index 000000000..a460c238e
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/ClientServerIncompatibleException.java
@@ -0,0 +1,16 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.exception;
+
+/**
+ * Thrown when the client and the remote server versions are incompatible.
+ */
+public class ClientServerIncompatibleException extends Exception {
+ public ClientServerIncompatibleException(String message) {
+ super("Current client is not compatible with the remote server, please check the "
+ + "version: " + message);
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/IOErrorException.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/IOErrorException.java
new file mode 100644
index 000000000..238ce1167
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/IOErrorException.java
@@ -0,0 +1,34 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.exception;
+
+/**
+ * Thrown when an IO error occurs while talking to the NebulaGraph server.
+ *
+ *
The {@code type} field mirrors the v3 client's error-type constants.
+ */
+public class IOErrorException extends java.lang.Exception {
+ public static final int E_UNKNOWN = 0;
+
+ public static final int E_ALL_BROKEN = 1;
+
+ public static final int E_CONNECT_BROKEN = 2;
+
+ public static final int E_TIME_OUT = 4;
+
+ public static final int E_NO_OPEN = 5;
+
+ private int type = E_UNKNOWN;
+
+ public IOErrorException(int errorType, String message) {
+ super(message);
+ this.type = errorType;
+ }
+
+ public int getType() {
+ return type;
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/InvalidConfigException.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/InvalidConfigException.java
new file mode 100644
index 000000000..c604f4caa
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/InvalidConfigException.java
@@ -0,0 +1,15 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.exception;
+
+/**
+ * Thrown when an illegal configuration is detected.
+ */
+public class InvalidConfigException extends RuntimeException {
+ public InvalidConfigException(String message) {
+ super(message);
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/InvalidSessionException.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/InvalidSessionException.java
new file mode 100644
index 000000000..fed70c69d
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/InvalidSessionException.java
@@ -0,0 +1,15 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.exception;
+
+/**
+ * Thrown when a released or invalidated session is used.
+ */
+public class InvalidSessionException extends RuntimeException {
+ public InvalidSessionException() {
+ super("The session was released, could not use again.");
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/InvalidValueException.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/InvalidValueException.java
new file mode 100644
index 000000000..70e146451
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/InvalidValueException.java
@@ -0,0 +1,15 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.exception;
+
+/**
+ * Thrown when a value is converted to an incompatible type.
+ */
+public class InvalidValueException extends RuntimeException {
+ public InvalidValueException(String message) {
+ super(message);
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/NotValidConnectionException.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/NotValidConnectionException.java
new file mode 100644
index 000000000..48419e7f6
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/exception/NotValidConnectionException.java
@@ -0,0 +1,15 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.exception;
+
+/**
+ * Thrown when a connection cannot be obtained from the pool.
+ */
+public class NotValidConnectionException extends Exception {
+ public NotValidConnectionException(String message) {
+ super(String.format("No extra connection: %s", message));
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/AuthResult.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/AuthResult.java
new file mode 100644
index 000000000..1a1f229cb
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/AuthResult.java
@@ -0,0 +1,35 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.net;
+
+import java.io.Serializable;
+
+/**
+ * Authentication result, matching the v3 client.
+ *
+ *
The v5 driver has no per-session timezone offset, so {@link #getTimezoneOffset()} always
+ * returns {@code 0}.
+ */
+public class AuthResult implements Serializable {
+
+ private static final long serialVersionUID = 8795815613377375650L;
+
+ private final long sessionId;
+ private final int timezoneOffset;
+
+ public AuthResult(long sessionId, int timezoneOffset) {
+ this.sessionId = sessionId;
+ this.timezoneOffset = timezoneOffset;
+ }
+
+ public long getSessionId() {
+ return sessionId;
+ }
+
+ public int getTimezoneOffset() {
+ return timezoneOffset;
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/ConnObjectPool.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/ConnObjectPool.java
new file mode 100644
index 000000000..d782b1638
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/ConnObjectPool.java
@@ -0,0 +1,69 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.net;
+
+import com.vesoft.nebula.driver.v3client.graph.NebulaPoolConfig;
+import java.io.Serializable;
+import org.apache.commons.pool2.BasePooledObjectFactory;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+
+/**
+ * Source-compatibility shim for the v3 client's connection object pool.
+ *
+ *
The v5 driver manages its own client pool; this factory is not used by
+ * {@link NebulaPool#getSession} and {@link #create()} is unsupported.
+ */
+public class ConnObjectPool extends BasePooledObjectFactory
+ implements Serializable {
+
+ private static final long serialVersionUID = 6101157301791971560L;
+
+ private final NebulaPoolConfig config;
+ private final LoadBalancer loadBalancer;
+
+ public ConnObjectPool(LoadBalancer loadBalancer, NebulaPoolConfig config) {
+ this.loadBalancer = loadBalancer;
+ this.config = config;
+ }
+
+ @Override
+ public SyncConnection create() throws Exception {
+ throw new UnsupportedOperationException(
+ "The v3 connection object pool is not used by the v5 driver.");
+ }
+
+ @Override
+ public PooledObject wrap(SyncConnection connection) {
+ return new DefaultPooledObject<>(connection);
+ }
+
+ @Override
+ public void destroyObject(PooledObject p) throws Exception {
+ p.getObject().close();
+ super.destroyObject(p);
+ }
+
+ @Override
+ public boolean validateObject(PooledObject p) {
+ return p.getObject() != null;
+ }
+
+ @SuppressWarnings("unused")
+ public boolean init() {
+ return loadBalancer.isServersOK();
+ }
+
+ @SuppressWarnings("unused")
+ public void updateServerStatus() {
+ loadBalancer.updateServersStatus();
+ }
+
+ @SuppressWarnings("unused")
+ public NebulaPoolConfig getConfig() {
+ return config;
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/Connection.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/Connection.java
new file mode 100644
index 000000000..6c946d125
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/Connection.java
@@ -0,0 +1,50 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.net;
+
+import com.vesoft.nebula.driver.v3client.graph.data.HostAddress;
+import com.vesoft.nebula.driver.v3client.graph.data.SSLParam;
+import com.vesoft.nebula.driver.v3client.graph.exception.ClientServerIncompatibleException;
+import com.vesoft.nebula.driver.v3client.graph.exception.IOErrorException;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * Abstract connection, matching the v3 client. The v5 driver manages connections internally via
+ * {@code GrpcConnection}, so this type exists for source compatibility only.
+ */
+public abstract class Connection implements Serializable {
+
+ private static final long serialVersionUID = -8425216612015802331L;
+
+ protected HostAddress serverAddr = null;
+
+ public HostAddress getServerAddress() {
+ return this.serverAddr;
+ }
+
+ public abstract void open(HostAddress address, int timeout, SSLParam sslParam)
+ throws IOErrorException, ClientServerIncompatibleException;
+
+ public abstract void open(HostAddress address, int timeout,
+ SSLParam sslParam, boolean isUseHttp2, Map headers)
+ throws IOErrorException, ClientServerIncompatibleException;
+
+ public abstract void open(HostAddress address, int timeout)
+ throws IOErrorException, ClientServerIncompatibleException;
+
+ public abstract void open(HostAddress address, int timeout,
+ boolean isUseHttp2, Map headers)
+ throws IOErrorException, ClientServerIncompatibleException;
+
+ public abstract void reopen() throws IOErrorException, ClientServerIncompatibleException;
+
+ public abstract void close();
+
+ public abstract boolean ping();
+
+ public abstract boolean ping(long sessionID);
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/LoadBalancer.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/LoadBalancer.java
new file mode 100644
index 000000000..2f7c9d07f
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/LoadBalancer.java
@@ -0,0 +1,21 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.net;
+
+import com.vesoft.nebula.driver.v3client.graph.data.HostAddress;
+
+/**
+ * Server load-balancer interface, matching the v3 client.
+ */
+public interface LoadBalancer {
+ HostAddress getAddress();
+
+ void close();
+
+ void updateServersStatus();
+
+ boolean isServersOK();
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/NebulaPool.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/NebulaPool.java
new file mode 100644
index 000000000..9d8a87e1b
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/NebulaPool.java
@@ -0,0 +1,269 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.net;
+
+import com.vesoft.nebula.driver.v3client.graph.NebulaPoolConfig;
+import com.vesoft.nebula.driver.v3client.graph.data.CASignedSSLParam;
+import com.vesoft.nebula.driver.v3client.graph.data.HostAddress;
+import com.vesoft.nebula.driver.v3client.graph.data.SSLParam;
+import com.vesoft.nebula.driver.v3client.graph.data.SelfSignedSSLParam;
+import com.vesoft.nebula.driver.v3client.graph.exception.AuthFailedException;
+import com.vesoft.nebula.driver.v3client.graph.exception.ClientServerIncompatibleException;
+import com.vesoft.nebula.driver.v3client.graph.exception.IOErrorException;
+import com.vesoft.nebula.driver.v3client.graph.exception.InvalidConfigException;
+import com.vesoft.nebula.driver.v3client.graph.exception.NotValidConnectionException;
+import java.io.Serializable;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A pool of connections/sessions, matching the v3 client's {@code NebulaPool} API.
+ *
+ *
The v3 pool is credential-less at init time and authenticates per {@link #getSession}; the v5
+ * driver bakes credentials into its client pool. This adapter therefore lazily builds one v5 pool
+ * per distinct username on the first {@code getSession} call.
+ */
+public class NebulaPool implements Serializable {
+
+ private static final long serialVersionUID = 6226487268001127885L;
+
+ private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+ private List addresses;
+ private NebulaPoolConfig config;
+
+ private final Map pools =
+ new HashMap<>();
+
+ private final AtomicBoolean hasInit = new AtomicBoolean(false);
+ private final AtomicBoolean isClosed = new AtomicBoolean(false);
+
+ private void checkConfig(NebulaPoolConfig config) {
+ if (config.getIdleTime() < 0) {
+ throw new InvalidConfigException(
+ "Config idleTime:" + config.getIdleTime() + " is illegal");
+ }
+ if (config.getMaxConnSize() <= 0) {
+ throw new InvalidConfigException(
+ "Config maxConnSize:" + config.getMaxConnSize() + " is illegal");
+ }
+ if (config.getMinConnSize() < 0 || config.getMinConnSize() > config.getMaxConnSize()) {
+ throw new InvalidConfigException(
+ "Config minConnSize:" + config.getMinConnSize() + " is illegal");
+ }
+ if (config.getTimeout() < 0) {
+ throw new InvalidConfigException(
+ "Config timeout:" + config.getTimeout() + " is illegal");
+ }
+ if (config.getWaitTime() < 0) {
+ throw new InvalidConfigException(
+ "Config waitTime:" + config.getWaitTime() + " is illegal");
+ }
+ if (config.getMinClusterHealthRate() < 0) {
+ throw new InvalidConfigException(
+ "Config minClusterHealthRate:" + config.getMinClusterHealthRate() + " is illegal");
+ }
+ }
+
+ /**
+ * @param addresses the graphd services addresses
+ * @param config the config for the pool
+ * @return {@code true} if the config is valid. The v5 driver performs the actual server
+ * health check lazily when the first session is requested.
+ * @throws UnknownHostException if host address is illegal
+ * @throws InvalidConfigException if config is illegal
+ */
+ public boolean init(List addresses, NebulaPoolConfig config)
+ throws UnknownHostException, InvalidConfigException {
+ checkInit();
+ checkConfig(config);
+ this.addresses = new ArrayList<>(addresses);
+ this.config = config;
+ hasInit.set(true);
+ return true;
+ }
+
+ /**
+ * close the pool, all connections will be closed.
+ */
+ public void close() {
+ if (isClosed.compareAndSet(false, true)) {
+ synchronized (pools) {
+ for (com.vesoft.nebula.driver.graph.net.NebulaPool pool : pools.values()) {
+ pool.close();
+ }
+ pools.clear();
+ }
+ }
+ }
+
+ /**
+ * get a session from the pool.
+ *
+ * @param userName the userName to authenticate with graphd
+ * @param password the password to authenticate with graphd
+ * @param reconnect whether to retry after the connection is disconnected
+ * @return Session
+ * @throws NotValidConnectionException if get connection failed
+ * @throws IOErrorException if an IO error occurs
+ * @throws AuthFailedException if authentication failed
+ */
+ public Session getSession(String userName, String password, boolean reconnect)
+ throws NotValidConnectionException, IOErrorException, AuthFailedException,
+ ClientServerIncompatibleException {
+ checkNoInitAndClosed();
+ com.vesoft.nebula.driver.graph.net.NebulaPool pool = getOrCreatePool(userName, password);
+ com.vesoft.nebula.driver.graph.net.NebulaClient client;
+ try {
+ client = pool.getClient();
+ } catch (Exception e) {
+ throw new NotValidConnectionException(e.getMessage());
+ }
+ return new Session(client, pool, reconnect);
+ }
+
+ public int getActiveConnNum() {
+ checkNoInitAndClosed();
+ int total = 0;
+ synchronized (pools) {
+ for (com.vesoft.nebula.driver.graph.net.NebulaPool pool : pools.values()) {
+ total += pool.getActiveSessions();
+ }
+ }
+ return total;
+ }
+
+ public int getIdleConnNum() {
+ checkNoInitAndClosed();
+ int total = 0;
+ synchronized (pools) {
+ for (com.vesoft.nebula.driver.graph.net.NebulaPool pool : pools.values()) {
+ total += pool.getIdleSessions();
+ }
+ }
+ return total;
+ }
+
+ public int getWaitersNum() {
+ checkNoInitAndClosed();
+ int total = 0;
+ synchronized (pools) {
+ for (com.vesoft.nebula.driver.graph.net.NebulaPool pool : pools.values()) {
+ total += pool.getWaiters();
+ }
+ }
+ return total;
+ }
+
+ private com.vesoft.nebula.driver.graph.net.NebulaPool getOrCreatePool(String user,
+ String password)
+ throws AuthFailedException, IOErrorException {
+ synchronized (pools) {
+ com.vesoft.nebula.driver.graph.net.NebulaPool pool = pools.get(user);
+ if (pool == null) {
+ pool = buildPool(user, password);
+ pools.put(user, pool);
+ }
+ return pool;
+ }
+ }
+
+ private com.vesoft.nebula.driver.graph.net.NebulaPool buildPool(String user, String password)
+ throws AuthFailedException, IOErrorException {
+ StringBuilder sb = new StringBuilder();
+ for (HostAddress address : addresses) {
+ if (sb.length() > 0) {
+ sb.append(',');
+ }
+ sb.append(address.toString());
+ }
+ com.vesoft.nebula.driver.graph.net.NebulaPool.Builder builder =
+ com.vesoft.nebula.driver.graph.net.NebulaPool.builder(sb.toString(), user, password);
+ builder.withMinClientSize(config.getMinConnSize());
+ builder.withMaxClientSize(config.getMaxConnSize());
+ if (config.getTimeout() > 0) {
+ builder.withConnectTimeoutMills(config.getTimeout());
+ builder.withRequestTimeoutMills(config.getTimeout());
+ }
+ if (config.getIdleTime() > 0) {
+ builder.withMinEvictableIdleTimeMillis(config.getIdleTime());
+ }
+ if (config.getIntervalIdle() > 0) {
+ builder.withIdleEvictScheduleMills(config.getIntervalIdle());
+ }
+ if (config.getWaitTime() > 0) {
+ builder.withMaxWaitMills(config.getWaitTime());
+ }
+ builder.withStrictlyServerHealthy(config.getMinClusterHealthRate() >= 1.0);
+ applyTls(builder, config);
+ try {
+ return builder.build();
+ } catch (com.vesoft.nebula.driver.graph.exception.AuthFailedException e) {
+ throw Session.toCompatAuth(e);
+ } catch (com.vesoft.nebula.driver.graph.exception.IOErrorException e) {
+ throw Session.toCompat(e);
+ }
+ }
+
+ private void applyTls(com.vesoft.nebula.driver.graph.net.NebulaPool.Builder builder,
+ NebulaPoolConfig config) {
+ if (!config.isEnableSsl()) {
+ return;
+ }
+ builder.withEnableTls(true);
+ SSLParam ssl = config.getSslParam();
+ if (ssl == null) {
+ builder.withDisableVerifyServerCert(true);
+ return;
+ }
+ if (ssl.isSkipVerifyServer()) {
+ builder.withDisableVerifyServerCert(true);
+ }
+ if (ssl instanceof CASignedSSLParam) {
+ CASignedSSLParam ca = (CASignedSSLParam) ssl;
+ if (ca.getCaCrtFilePath() != null) {
+ builder.withTlsCa(ca.getCaCrtFilePath());
+ }
+ if (ca.getCrtFilePath() != null && ca.getKeyFilePath() != null) {
+ builder.withTlsCert(ca.getCrtFilePath(), ca.getKeyFilePath());
+ }
+ } else if (ssl instanceof SelfSignedSSLParam) {
+ SelfSignedSSLParam self = (SelfSignedSSLParam) ssl;
+ builder.withDisableVerifyServerCert(true);
+ if (self.getCrtFilePath() != null && self.getKeyFilePath() != null) {
+ builder.withTlsCert(self.getCrtFilePath(), self.getKeyFilePath());
+ }
+ }
+ }
+
+ private void checkNoInit() {
+ if (!hasInit.get()) {
+ throw new RuntimeException(
+ "The pool has not been initialized, please initialize it first.");
+ }
+ }
+
+ private void checkInit() {
+ if (hasInit.get()) {
+ throw new RuntimeException(
+ "The pool has already been initialized. "
+ + "Please do not initialize the pool repeatedly.");
+ }
+ }
+
+ private void checkNoInitAndClosed() {
+ checkNoInit();
+ if (isClosed.get()) {
+ throw new RuntimeException("The pool has closed. Couldn't use again.");
+ }
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/RoundRobinLoadBalancer.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/RoundRobinLoadBalancer.java
new file mode 100644
index 000000000..aa6affec9
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/RoundRobinLoadBalancer.java
@@ -0,0 +1,80 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.net;
+
+import com.vesoft.nebula.driver.v3client.graph.data.HostAddress;
+import com.vesoft.nebula.driver.v3client.graph.data.SSLParam;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Source-compatibility shim for the v3 client's round-robin load balancer.
+ *
+ *
The v5 driver performs its own server health management, so this shim only provides a
+ * round-robin {@link #getAddress()} over the configured addresses.
+ */
+public class RoundRobinLoadBalancer implements LoadBalancer {
+
+ private final List addresses = new ArrayList<>();
+ private final AtomicInteger pos = new AtomicInteger(0);
+
+ public RoundRobinLoadBalancer(List addresses, int timeout,
+ double minClusterHealthRate) {
+ this(addresses);
+ }
+
+ public RoundRobinLoadBalancer(List addresses, int timeout,
+ double minClusterHealthRate, boolean useHttp2,
+ Map headers) {
+ this(addresses);
+ }
+
+ public RoundRobinLoadBalancer(List addresses, int timeout, SSLParam sslParam,
+ double minClusterHealthRate) {
+ this(addresses);
+ }
+
+ public RoundRobinLoadBalancer(List addresses, int timeout, SSLParam sslParam,
+ double minClusterHealthRate, boolean useHttp2,
+ Map headers) {
+ this(addresses);
+ }
+
+ private RoundRobinLoadBalancer(List addresses) {
+ if (addresses != null) {
+ this.addresses.addAll(addresses);
+ }
+ }
+
+ @Override
+ public HostAddress getAddress() {
+ if (addresses.isEmpty()) {
+ return null;
+ }
+ return addresses.get(Math.abs(pos.getAndIncrement()) % addresses.size());
+ }
+
+ @Override
+ public void close() {
+ // no-op
+ }
+
+ @Override
+ public void updateServersStatus() {
+ // no-op; health is managed by the v5 driver.
+ }
+
+ @Override
+ public boolean isServersOK() {
+ return !addresses.isEmpty();
+ }
+
+ @SuppressWarnings("unused")
+ private static final Map EMPTY = new HashMap<>();
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/Session.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/Session.java
new file mode 100644
index 000000000..d60e9a6bf
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/Session.java
@@ -0,0 +1,372 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.net;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.vesoft.nebula.driver.v3client.graph.data.HostAddress;
+import com.vesoft.nebula.driver.v3client.graph.data.ResultSet;
+import com.vesoft.nebula.driver.v3client.graph.data.ValueWrapper;
+import com.vesoft.nebula.driver.v3client.graph.exception.AuthFailedException;
+import com.vesoft.nebula.driver.v3client.graph.exception.IOErrorException;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A session against NebulaGraph, matching the v3 client's {@code Session} API.
+ *
+ *
Internally it delegates to a v5 driver {@link com.vesoft.nebula.driver.graph.net.NebulaClient};
+ * one v5 client is one server-side session.
+ */
+public class Session implements Serializable, AutoCloseable {
+
+ private static final long serialVersionUID = -8855886967097862376L;
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private final long sessionID;
+ private final int timezoneOffset;
+ private final com.vesoft.nebula.driver.graph.net.NebulaPool pool;
+ private final boolean retryConnect;
+ private final AtomicBoolean released = new AtomicBoolean(false);
+ private volatile com.vesoft.nebula.driver.graph.net.NebulaClient client;
+
+ public Session(com.vesoft.nebula.driver.graph.net.NebulaClient client,
+ com.vesoft.nebula.driver.graph.net.NebulaPool pool,
+ boolean retryConnect) {
+ this.client = client;
+ this.sessionID = client.getSessionId();
+ this.timezoneOffset = 0;
+ this.pool = pool;
+ this.retryConnect = retryConnect;
+ }
+
+ public synchronized ResultSet execute(String stmt) throws IOErrorException {
+ return executeWithParameter(stmt, Collections.emptyMap());
+ }
+
+ public synchronized ResultSet executeWithParameter(String stmt, Map parameterMap)
+ throws IOErrorException {
+ checkReleased();
+ String gql = inlineParameters(stmt, parameterMap);
+ try {
+ return new ResultSet(client.execute(gql), timezoneOffset);
+ } catch (com.vesoft.nebula.driver.graph.exception.IOErrorException e) {
+ if (isConnectBroken(e) && retryConnect && reconnect()) {
+ try {
+ return new ResultSet(client.execute(gql), timezoneOffset);
+ } catch (com.vesoft.nebula.driver.graph.exception.IOErrorException e2) {
+ throw toCompat(e2);
+ }
+ }
+ throw toCompat(e);
+ }
+ }
+
+ public ResultSet executeWithTimeout(String stmt, long timeoutMs) throws IOErrorException {
+ return executeWithParameterTimeout(stmt, Collections.emptyMap(), timeoutMs);
+ }
+
+ public synchronized ResultSet executeWithParameterTimeout(String stmt,
+ Map parameterMap,
+ long timeoutMs)
+ throws IOErrorException {
+ checkReleased();
+ if (timeoutMs <= 0) {
+ throw new IllegalArgumentException("timeout should be a positive number");
+ }
+ String gql = inlineParameters(stmt, parameterMap);
+ try {
+ return new ResultSet(client.execute(gql, timeoutMs), timezoneOffset);
+ } catch (com.vesoft.nebula.driver.graph.exception.IOErrorException e) {
+ if (isConnectBroken(e) && retryConnect && reconnect()) {
+ try {
+ return new ResultSet(client.execute(gql, timeoutMs), timezoneOffset);
+ } catch (com.vesoft.nebula.driver.graph.exception.IOErrorException e2) {
+ throw toCompat(e2);
+ }
+ }
+ throw toCompat(e);
+ }
+ }
+
+ public synchronized String executeJson(String stmt) throws IOErrorException {
+ return executeJsonWithParameter(stmt, Collections.emptyMap());
+ }
+
+ public synchronized String executeJsonWithParameter(String stmt,
+ Map parameterMap)
+ throws IOErrorException {
+ ResultSet resultSet = executeWithParameter(stmt, parameterMap);
+ return toJson(resultSet);
+ }
+
+ public synchronized boolean ping() {
+ if (client == null) {
+ return false;
+ }
+ return client.ping();
+ }
+
+ public synchronized boolean pingSession() {
+ if (client == null) {
+ return false;
+ }
+ return client.ping();
+ }
+
+ public synchronized void release() {
+ if (client == null) {
+ return;
+ }
+ if (released.compareAndSet(false, true)) {
+ if (pool != null) {
+ pool.returnClient(client);
+ } else {
+ client.close();
+ }
+ client = null;
+ }
+ }
+
+ public synchronized HostAddress getGraphHost() {
+ if (client == null) {
+ return null;
+ }
+ return parseHost(client.getHost());
+ }
+
+ public long getSessionID() {
+ return sessionID;
+ }
+
+ @Override
+ public synchronized void close() {
+ release();
+ }
+
+ private void checkReleased() throws IOErrorException {
+ if (client == null) {
+ throw new IOErrorException(IOErrorException.E_CONNECT_BROKEN,
+ "The session was released, couldn't use again.");
+ }
+ }
+
+ private boolean reconnect() {
+ if (pool == null) {
+ return false;
+ }
+ client.close();
+ pool.returnClient(client);
+ try {
+ client = pool.getClient();
+ return true;
+ } catch (Exception e) {
+ log.error("Reconnect failed: " + e);
+ return false;
+ }
+ }
+
+ public static AuthFailedException toCompatAuth(
+ com.vesoft.nebula.driver.graph.exception.AuthFailedException e) {
+ String msg = e.getMessage();
+ String prefix = "Auth failed: ";
+ if (msg != null && msg.startsWith(prefix)) {
+ msg = msg.substring(prefix.length());
+ }
+ return new AuthFailedException(msg);
+ }
+
+ public static boolean isConnectBroken(
+ com.vesoft.nebula.driver.graph.exception.IOErrorException e) {
+ return e.getType() == com.vesoft.nebula.driver.graph.exception.IOErrorException
+ .E_CONNECT_BROKEN;
+ }
+
+ public static IOErrorException toCompat(
+ com.vesoft.nebula.driver.graph.exception.IOErrorException e) {
+ int type;
+ switch (e.getType()) {
+ case com.vesoft.nebula.driver.graph.exception.IOErrorException.E_CONNECT_BROKEN:
+ type = IOErrorException.E_CONNECT_BROKEN;
+ break;
+ case com.vesoft.nebula.driver.graph.exception.IOErrorException.E_ALL_BROKEN:
+ type = IOErrorException.E_ALL_BROKEN;
+ break;
+ case com.vesoft.nebula.driver.graph.exception.IOErrorException.E_TIME_OUT:
+ type = IOErrorException.E_TIME_OUT;
+ break;
+ case com.vesoft.nebula.driver.graph.exception.IOErrorException.E_NO_OPEN:
+ type = IOErrorException.E_NO_OPEN;
+ break;
+ default:
+ type = IOErrorException.E_UNKNOWN;
+ }
+ return new IOErrorException(type, e.getMessage());
+ }
+
+ public static String toJson(ResultSet resultSet) {
+ JSONObject root = new JSONObject();
+ JSONArray errors = new JSONArray();
+ JSONObject error = new JSONObject();
+ error.put("code", resultSet.getErrorCode());
+ error.put("message", resultSet.getErrorMessage());
+ errors.add(error);
+ root.put("errors", errors);
+
+ JSONArray results = new JSONArray();
+ JSONObject result = new JSONObject();
+ result.put("columns", resultSet.getColumnNames());
+ result.put("latencyInUs", resultSet.getLatency());
+ result.put("spaceName", resultSet.getSpaceName());
+ result.put("comment", resultSet.getComment());
+ JSONArray data = new JSONArray();
+ for (int i = 0; i < resultSet.rowsSize(); i++) {
+ JSONObject rowObj = new JSONObject();
+ JSONArray row = new JSONArray();
+ for (ValueWrapper value : resultSet.rowValues(i)) {
+ row.add(unquote(value));
+ }
+ rowObj.put("row", row);
+ rowObj.put("meta", JSON.parseObject("{}"));
+ data.add(rowObj);
+ }
+ result.put("data", data);
+ results.add(result);
+ root.put("results", results);
+ return JSON.toJSONString(root);
+ }
+
+ private static Object unquote(ValueWrapper value) {
+ String str = value.toString();
+ if (str != null && str.length() >= 2 && str.startsWith("\"") && str.endsWith("\"")) {
+ return str.substring(1, str.length() - 1);
+ }
+ return str;
+ }
+
+ /**
+ * Inline {@code $param} placeholders with GQL literals (the v5 execute RPC takes no parameter
+ * map). Keys are replaced longest-first to avoid prefix collisions.
+ */
+ public static String inlineParameters(String stmt, Map parameterMap) {
+ if (parameterMap == null || parameterMap.isEmpty()) {
+ return stmt;
+ }
+ String gql = stmt;
+ List keys = new ArrayList<>(parameterMap.keySet());
+ keys.sort((a, b) -> Integer.compare(b.length(), a.length()));
+ for (String key : keys) {
+ gql = gql.replace("$" + key, value2GqlLiteral(parameterMap.get(key)));
+ }
+ return gql;
+ }
+
+ /**
+ * Convert a Java value to a GQL literal. Supports the v3 client's parameter value types:
+ * null, boolean, numeric, string, bytes, list/collection, map, and falls back to a quoted
+ * string for other types.
+ */
+ public static String value2GqlLiteral(Object value) {
+ if (value == null) {
+ return "NULL";
+ }
+ if (value instanceof Boolean
+ || value instanceof Integer
+ || value instanceof Short
+ || value instanceof Byte
+ || value instanceof Long
+ || value instanceof Float
+ || value instanceof Double) {
+ return String.valueOf(value);
+ }
+ if (value instanceof String) {
+ return "\"" + escape((String) value) + "\"";
+ }
+ if (value instanceof byte[]) {
+ return "\"" + escape(new String((byte[]) value, StandardCharsets.UTF_8)) + "\"";
+ }
+ if (value instanceof Character) {
+ return "\"" + escape(value.toString()) + "\"";
+ }
+ if (value instanceof Collection) {
+ List literals = new ArrayList<>();
+ for (Object element : (Collection>) value) {
+ literals.add(value2GqlLiteral(element));
+ }
+ return "[" + String.join(", ", literals) + "]";
+ }
+ if (value instanceof Map) {
+ List entries = new ArrayList<>();
+ for (Map.Entry, ?> entry : ((Map, ?>) value).entrySet()) {
+ entries.add(entry.getKey().toString() + ": " + value2GqlLiteral(entry.getValue()));
+ }
+ return "{" + String.join(", ", entries) + "}";
+ }
+ return "\"" + escape(value.toString()) + "\"";
+ }
+
+ private static String escape(String value) {
+ StringBuilder builder = new StringBuilder();
+ for (char c : value.toCharArray()) {
+ switch (c) {
+ case '\\':
+ builder.append("\\\\");
+ break;
+ case '"':
+ builder.append("\\\"");
+ break;
+ case '\t':
+ builder.append("\\t");
+ break;
+ case '\n':
+ builder.append("\\n");
+ break;
+ case '\r':
+ builder.append("\\r");
+ break;
+ case '\b':
+ builder.append("\\b");
+ break;
+ case '\'':
+ builder.append("\\'");
+ break;
+ default:
+ builder.append(c);
+ break;
+ }
+ }
+ return builder.toString();
+ }
+
+ public static HostAddress parseHost(String host) {
+ if (host == null || host.isEmpty()) {
+ return null;
+ }
+ if (host.startsWith("[")) {
+ int close = host.indexOf(']');
+ if (close < 0) {
+ return new HostAddress(host, 0);
+ }
+ return new HostAddress(host.substring(1, close),
+ Integer.parseInt(host.substring(close + 2)));
+ }
+ int idx = host.lastIndexOf(':');
+ if (idx < 0) {
+ return new HostAddress(host, 0);
+ }
+ return new HostAddress(host.substring(0, idx), Integer.parseInt(host.substring(idx + 1)));
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/SessionState.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/SessionState.java
new file mode 100644
index 000000000..08234fcbd
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/SessionState.java
@@ -0,0 +1,13 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.net;
+
+/**
+ * The lifecycle state of a pooled session, matching the v3 client.
+ */
+public enum SessionState {
+ IDLE, USED
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/SessionWrapper.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/SessionWrapper.java
new file mode 100644
index 000000000..32a3814cc
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/SessionWrapper.java
@@ -0,0 +1,62 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.net;
+
+import com.vesoft.nebula.driver.v3client.graph.data.ResultSet;
+import com.vesoft.nebula.driver.v3client.graph.exception.IOErrorException;
+import com.vesoft.nebula.driver.v3client.graph.exception.InvalidSessionException;
+import java.io.Serializable;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A single-use wrapper around a {@link Session}, matching the v3 client.
+ */
+public class SessionWrapper implements Serializable {
+
+ private static final long serialVersionUID = -8128331485649098264L;
+
+ private final Session session;
+ private final long sessionID;
+ private final AtomicBoolean available = new AtomicBoolean(true);
+
+ public SessionWrapper(Session session) {
+ this.session = session;
+ this.sessionID = session.getSessionID();
+ }
+
+ public ResultSet execute(String stmt) throws IOErrorException {
+ if (!available()) {
+ throw new InvalidSessionException();
+ }
+ return session.execute(stmt);
+ }
+
+ public boolean ping() {
+ return session.pingSession();
+ }
+
+ void setNoAvailable() {
+ this.available.set(false);
+ }
+
+ boolean available() {
+ return available.get();
+ }
+
+ void release() {
+ session.release();
+ setNoAvailable();
+ }
+
+ Session getSession() {
+ return session;
+ }
+
+ @SuppressWarnings("unused")
+ public long getSessionID() {
+ return sessionID;
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/SessionsManager.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/SessionsManager.java
new file mode 100644
index 000000000..8a53399b4
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/SessionsManager.java
@@ -0,0 +1,127 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.net;
+
+import com.vesoft.nebula.driver.v3client.graph.SessionsManagerConfig;
+import com.vesoft.nebula.driver.v3client.graph.data.ResultSet;
+import com.vesoft.nebula.driver.v3client.graph.exception.AuthFailedException;
+import com.vesoft.nebula.driver.v3client.graph.exception.ClientServerIncompatibleException;
+import com.vesoft.nebula.driver.v3client.graph.exception.IOErrorException;
+import com.vesoft.nebula.driver.v3client.graph.exception.NotValidConnectionException;
+import java.io.Serializable;
+import java.net.UnknownHostException;
+import java.util.BitSet;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Manages a set of single-use sessions, matching the v3 client's {@code SessionsManager}.
+ */
+public class SessionsManager implements Serializable {
+
+ private static final long serialVersionUID = 7519424097351713021L;
+
+ private final SessionsManagerConfig config;
+ private NebulaPool pool = null;
+ private final CopyOnWriteArrayList sessionList;
+ private BitSet canUseBitSet;
+ private Boolean isClose = false;
+ private Boolean isInited = false;
+
+ public SessionsManager(SessionsManagerConfig config) {
+ this.config = config;
+ this.sessionList = new CopyOnWriteArrayList<>();
+ checkConfig();
+ }
+
+ private void checkConfig() {
+ if (config.getAddresses().isEmpty()) {
+ throw new RuntimeException("Empty graph addresses");
+ }
+ if (config.getSpaceName().isEmpty()) {
+ throw new RuntimeException("Empty space name");
+ }
+ }
+
+ public synchronized SessionWrapper getSessionWrapper()
+ throws RuntimeException, ClientServerIncompatibleException {
+ checkClose();
+ if (!isInited) {
+ init();
+ }
+ if (canUseBitSet.isEmpty()
+ && sessionList.size() >= config.getPoolConfig().getMaxConnSize()) {
+ throw new RuntimeException("The SessionsManager does not have available sessions.");
+ }
+ if (!canUseBitSet.isEmpty()) {
+ int index = canUseBitSet.nextSetBit(0);
+ if (index >= 0) {
+ if (canUseBitSet.get(index)) {
+ canUseBitSet.set(index, false);
+ return sessionList.get(index);
+ }
+ }
+ }
+ try {
+ Session session = pool.getSession(
+ config.getUserName(), config.getPassword(), config.getReconnect());
+ ResultSet resultSet = session.execute(
+ String.format("SESSION SET GRAPH \"%s\"", config.getSpaceName()));
+ if (!resultSet.isSucceeded()) {
+ throw new RuntimeException(
+ "Switch graph `" + config.getSpaceName() + "' failed: "
+ + resultSet.getErrorMessage());
+ }
+ SessionWrapper sessionWrapper = new SessionWrapper(session);
+ sessionList.add(sessionWrapper);
+ return sessionWrapper;
+ } catch (AuthFailedException | NotValidConnectionException | IOErrorException e) {
+ throw new RuntimeException("Get session failed: " + e.getMessage());
+ }
+ }
+
+ public synchronized void returnSessionWrapper(SessionWrapper session) {
+ checkClose();
+ if (session == null) {
+ return;
+ }
+ int index = sessionList.indexOf(session);
+ if (index >= 0) {
+ Session ses = session.getSession();
+ sessionList.set(index, new SessionWrapper(ses));
+ session.setNoAvailable();
+ canUseBitSet.set(index, true);
+ }
+ }
+
+ public synchronized void close() {
+ for (SessionWrapper session : sessionList) {
+ session.release();
+ }
+ pool.close();
+ sessionList.clear();
+ isClose = true;
+ }
+
+ private void init() throws RuntimeException {
+ try {
+ pool = new NebulaPool();
+ if (!pool.init(config.getAddresses(), config.getPoolConfig())) {
+ throw new RuntimeException("Init pool failed: services are broken.");
+ }
+ canUseBitSet = new BitSet(config.getPoolConfig().getMaxConnSize());
+ canUseBitSet.set(0, config.getPoolConfig().getMaxConnSize(), false);
+ } catch (UnknownHostException e) {
+ throw new RuntimeException("Init the pool failed: " + e.getMessage());
+ }
+ isInited = true;
+ }
+
+ private void checkClose() {
+ if (isClose) {
+ throw new RuntimeException("The SessionsManager was closed.");
+ }
+ }
+}
diff --git a/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/SyncConnection.java b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/SyncConnection.java
new file mode 100644
index 000000000..1b4bf803d
--- /dev/null
+++ b/client-v3compat/src/main/java/com/vesoft/nebula/driver/v3client/graph/net/SyncConnection.java
@@ -0,0 +1,90 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph.net;
+
+import com.vesoft.nebula.driver.v3client.graph.data.HostAddress;
+import com.vesoft.nebula.driver.v3client.graph.data.SSLParam;
+import com.vesoft.nebula.driver.v3client.graph.exception.AuthFailedException;
+import com.vesoft.nebula.driver.v3client.graph.exception.ClientServerIncompatibleException;
+import com.vesoft.nebula.driver.v3client.graph.exception.IOErrorException;
+import java.util.Map;
+
+/**
+ * Source-compatibility shim for the v3 client's {@code SyncConnection}.
+ *
+ *
The v5 driver talks gRPC and manages connections internally; applications should obtain
+ * sessions through {@link NebulaPool#getSession}. Direct use of the connection-level API is not
+ * supported and the authenticate/execute methods throw {@link UnsupportedOperationException}.
+ */
+public class SyncConnection extends Connection {
+
+ private SSLParam sslParam = null;
+ private int timeout = 0;
+ private boolean useHttp2 = false;
+ private Map headers = null;
+ private boolean opened = false;
+
+ @Override
+ public void open(HostAddress address, int timeout, SSLParam sslParam)
+ throws IOErrorException, ClientServerIncompatibleException {
+ open(address, timeout, sslParam, false, null);
+ }
+
+ @Override
+ public void open(HostAddress address, int timeout, SSLParam sslParam, boolean isUseHttp2,
+ Map headers)
+ throws IOErrorException, ClientServerIncompatibleException {
+ this.serverAddr = address;
+ this.timeout = timeout;
+ this.sslParam = sslParam;
+ this.useHttp2 = isUseHttp2;
+ this.headers = headers;
+ this.opened = true;
+ }
+
+ @Override
+ public void open(HostAddress address, int timeout)
+ throws IOErrorException, ClientServerIncompatibleException {
+ open(address, timeout, null, false, null);
+ }
+
+ @Override
+ public void open(HostAddress address, int timeout, boolean isUseHttp2,
+ Map headers)
+ throws IOErrorException, ClientServerIncompatibleException {
+ open(address, timeout, null, isUseHttp2, headers);
+ }
+
+ @Override
+ public void reopen() throws IOErrorException, ClientServerIncompatibleException {
+ close();
+ if (serverAddr != null) {
+ open(serverAddr, timeout, sslParam, useHttp2, headers);
+ }
+ }
+
+ @Override
+ public void close() {
+ opened = false;
+ }
+
+ @Override
+ public boolean ping() {
+ return false;
+ }
+
+ @Override
+ public boolean ping(long sessionID) {
+ return false;
+ }
+
+ public AuthResult authenticate(String user, String password)
+ throws AuthFailedException, IOErrorException, ClientServerIncompatibleException {
+ throw new UnsupportedOperationException(
+ "Direct connection authentication is not supported by the v5 driver; "
+ + "use NebulaPool.getSession(user, password, reconnect).");
+ }
+}
diff --git a/client-v3compat/src/test/java/com/vesoft/nebula/driver/v3client/graph/V3IntegrationTest.java b/client-v3compat/src/test/java/com/vesoft/nebula/driver/v3client/graph/V3IntegrationTest.java
new file mode 100644
index 000000000..bb264de72
--- /dev/null
+++ b/client-v3compat/src/test/java/com/vesoft/nebula/driver/v3client/graph/V3IntegrationTest.java
@@ -0,0 +1,199 @@
+/* Copyright (c) 2025 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+package com.vesoft.nebula.driver.v3client.graph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.vesoft.nebula.driver.v3client.graph.data.HostAddress;
+import com.vesoft.nebula.driver.v3client.graph.data.Node;
+import com.vesoft.nebula.driver.v3client.graph.data.PathWrapper;
+import com.vesoft.nebula.driver.v3client.graph.data.Relationship;
+import com.vesoft.nebula.driver.v3client.graph.data.ResultSet;
+import com.vesoft.nebula.driver.v3client.graph.data.ValueWrapper;
+import com.vesoft.nebula.driver.v3client.graph.net.NebulaPool;
+import com.vesoft.nebula.driver.v3client.graph.net.Session;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import org.junit.AfterClass;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * End-to-end integration test for the v3-compatible layer against a live NebulaGraph v5 cluster.
+ *
+ *
Disabled by default (so plain {@code mvn test} stays green without a cluster). Enable and
+ * point it at a cluster with:
+ *
+ *