From 372861b7bfbce5d10f66959d3cc9f9ad010b94d4 Mon Sep 17 00:00:00 2001 From: Cathleen Yan Date: Wed, 19 Aug 2026 20:44:15 +0000 Subject: [PATCH 1/3] Fix validation for incomplete JDBC URLs Signed-off-by: Cathleen Yan --- NEXT_CHANGELOG.md | 3 ++ .../jdbc/common/util/ValidationUtil.java | 36 +++++++++++++++++++ .../databricks/client/jdbc/DriverTest.java | 24 +++++++++++++ .../impl/DatabricksConnectionContextTest.java | 21 +++++++++++ 4 files changed, 84 insertions(+) create mode 100644 src/test/java/com/databricks/client/jdbc/DriverTest.java diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index e12c8875f4..d6f09f3929 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -8,6 +8,9 @@ - `DatabaseMetaData.getColumns(...)` with a `null` catalog now issues a single `SHOW COLUMNS IN ALL CATALOGS` statement (consistent with `getSchemas`/`getTables`) instead of enumerating every catalog and issuing a per-catalog `SHOW COLUMNS`. Older DBR versions that do not support the syntax transparently fall back to the previous enumerate-and-fan-out behavior. ### Fixed +- Invalid or incomplete Databricks JDBC URLs now fail with a descriptive `DatabricksSQLException` + instead of leaking a `NullPointerException` when required connection parameters are missing. + - Fixed `IdleConnectionEvictor` thread leak in long-running applications. Driver-side resources (HTTP client, background threads) are now always released when `Connection.close()` is called, even if statement cleanup or server-side session termination fails. - Throw `DatabricksSQLException` instead of an unchecked `ClassCastException` when a complex-type getter (`getArray`, `getStruct`, `getMap`) is called on a column of a different complex type. diff --git a/src/main/java/com/databricks/jdbc/common/util/ValidationUtil.java b/src/main/java/com/databricks/jdbc/common/util/ValidationUtil.java index 4c29953100..7d066af1d8 100644 --- a/src/main/java/com/databricks/jdbc/common/util/ValidationUtil.java +++ b/src/main/java/com/databricks/jdbc/common/util/ValidationUtil.java @@ -11,6 +11,7 @@ import com.databricks.jdbc.log.JdbcLoggerFactory; import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Pattern; @@ -20,6 +21,8 @@ public class ValidationUtil { private static final JdbcLogger LOGGER = JdbcLoggerFactory.getLogger(ValidationUtil.class); + private static final List + UNCONDITIONALLY_REQUIRED_CONNECTION_PARAMETERS = List.of(DatabricksJdbcUrlParams.HTTP_PATH); public static void checkIfNonNegative(T number, String fieldName) throws DatabricksValidationException { @@ -148,6 +151,10 @@ public static void checkHTTPError(HttpResponse response) * @return true if the URL is valid, false otherwise */ public static boolean isValidJdbcUrl(String url) { + if (url == null) { + return false; + } + final List PATH_PATTERNS = List.of( HTTP_CLUSTER_PATH_PATTERN, @@ -176,11 +183,40 @@ public static boolean isValidJdbcUrl(String url) { */ public static void validateInputProperties(Map parameters) throws DatabricksValidationException { + validateRequiredConnectionParameters(parameters); // Fail fast on an unsupported AuthMech before the client-configurator machinery runs. validateAuthMech(parameters); validateUidParameter(parameters); } + /** + * Validates parameters that must be present in every connection configuration. URL parameters and + * {@link java.util.Properties} are merged before this method is called, so required values may be + * supplied through either mechanism. + * + * @param parameters merged JDBC connection parameters + * @throws DatabricksValidationException if any required parameter is missing or blank + */ + private static void validateRequiredConnectionParameters(Map parameters) + throws DatabricksValidationException { + List missingParameters = new ArrayList<>(); + for (DatabricksJdbcUrlParams requiredParameter : + UNCONDITIONALLY_REQUIRED_CONNECTION_PARAMETERS) { + String parameterName = requiredParameter.getParamName().toLowerCase(); + String value = parameters.get(parameterName); + if (value == null || value.isBlank()) { + missingParameters.add(parameterName); + } + } + if (!missingParameters.isEmpty()) { + String parameterLabel = missingParameters.size() == 1 ? "parameter" : "parameters"; + throw new DatabricksValidationException( + String.format( + "Missing required connection %s: %s", + parameterLabel, String.join(", ", missingParameters))); + } + } + /** * Validates the AuthMech parameter. Reuses {@link AuthMech#fromValue} as the single source of * truth for supported values, so adding a new AuthMech only requires updating {@code AuthMech}. diff --git a/src/test/java/com/databricks/client/jdbc/DriverTest.java b/src/test/java/com/databricks/client/jdbc/DriverTest.java new file mode 100644 index 0000000000..d88b878080 --- /dev/null +++ b/src/test/java/com/databricks/client/jdbc/DriverTest.java @@ -0,0 +1,24 @@ +package com.databricks.client.jdbc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.databricks.jdbc.exception.DatabricksSQLException; +import java.util.Properties; +import org.junit.jupiter.api.Test; + +class DriverTest { + + @Test + void connectRejectsMissingRequiredConnectionParameters() { + DatabricksSQLException exception = + assertThrows( + DatabricksSQLException.class, + () -> + Driver.getInstance().connect("jdbc:databricks://localhost:8080", new Properties())); + + assertEquals("INPUT_VALIDATION_ERROR", exception.getSQLState()); + assertTrue(exception.getMessage().contains("httppath")); + } +} diff --git a/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionContextTest.java b/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionContextTest.java index 2d68683dec..7697f85034 100644 --- a/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionContextTest.java +++ b/src/test/java/com/databricks/jdbc/api/impl/DatabricksConnectionContextTest.java @@ -33,6 +33,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; class DatabricksConnectionContextTest { @@ -85,6 +86,26 @@ public void testParseInvalid() { assertThrows( DatabricksParsingException.class, () -> DatabricksConnectionContext.parse(TestConstants.INVALID_URL_2, properties)); + assertThrows( + DatabricksParsingException.class, + () -> DatabricksConnectionContext.parse(null, properties)); + } + + @ParameterizedTest + @ValueSource( + strings = { + "jdbc:databricks://localhost:8080", + "jdbc:databricks://localhost:8080;httpPath=", + "jdbc:databricks://localhost:8080;httpPath= " + }) + public void testParseRejectsMissingRequiredConnectionParameters(String url) { + DatabricksSQLException exception = + assertThrows( + DatabricksSQLException.class, + () -> DatabricksConnectionContext.parse(url, new Properties())); + + assertEquals("INPUT_VALIDATION_ERROR", exception.getSQLState()); + assertTrue(exception.getMessage().contains("httppath")); } @Test From eda7fa5efcfb37401404869a2286b435c9419dec Mon Sep 17 00:00:00 2001 From: Cathleen Yan Date: Wed, 19 Aug 2026 20:59:45 +0000 Subject: [PATCH 2/3] Simplify required HTTP path validation Signed-off-by: Cathleen Yan --- .../jdbc/common/util/ValidationUtil.java | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/databricks/jdbc/common/util/ValidationUtil.java b/src/main/java/com/databricks/jdbc/common/util/ValidationUtil.java index 7d066af1d8..752ab58b58 100644 --- a/src/main/java/com/databricks/jdbc/common/util/ValidationUtil.java +++ b/src/main/java/com/databricks/jdbc/common/util/ValidationUtil.java @@ -11,7 +11,6 @@ import com.databricks.jdbc.log.JdbcLoggerFactory; import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Pattern; @@ -21,9 +20,6 @@ public class ValidationUtil { private static final JdbcLogger LOGGER = JdbcLoggerFactory.getLogger(ValidationUtil.class); - private static final List - UNCONDITIONALLY_REQUIRED_CONNECTION_PARAMETERS = List.of(DatabricksJdbcUrlParams.HTTP_PATH); - public static void checkIfNonNegative(T number, String fieldName) throws DatabricksValidationException { if (number.longValue() < 0) { @@ -199,21 +195,11 @@ public static void validateInputProperties(Map parameters) */ private static void validateRequiredConnectionParameters(Map parameters) throws DatabricksValidationException { - List missingParameters = new ArrayList<>(); - for (DatabricksJdbcUrlParams requiredParameter : - UNCONDITIONALLY_REQUIRED_CONNECTION_PARAMETERS) { - String parameterName = requiredParameter.getParamName().toLowerCase(); - String value = parameters.get(parameterName); - if (value == null || value.isBlank()) { - missingParameters.add(parameterName); - } - } - if (!missingParameters.isEmpty()) { - String parameterLabel = missingParameters.size() == 1 ? "parameter" : "parameters"; + String parameterName = DatabricksJdbcUrlParams.HTTP_PATH.getParamName().toLowerCase(); + String httpPath = parameters.get(parameterName); + if (httpPath == null || httpPath.isBlank()) { throw new DatabricksValidationException( - String.format( - "Missing required connection %s: %s", - parameterLabel, String.join(", ", missingParameters))); + "Missing required connection parameter: " + parameterName); } } From ac64ede90abe6b2e08d50e1ba2a783981d5f920b Mon Sep 17 00:00:00 2001 From: Cathleen Yan Date: Wed, 19 Aug 2026 21:08:00 +0000 Subject: [PATCH 3/3] Restore ValidationUtil spacing Signed-off-by: Cathleen Yan --- .../java/com/databricks/jdbc/common/util/ValidationUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/databricks/jdbc/common/util/ValidationUtil.java b/src/main/java/com/databricks/jdbc/common/util/ValidationUtil.java index 752ab58b58..d32f734e0f 100644 --- a/src/main/java/com/databricks/jdbc/common/util/ValidationUtil.java +++ b/src/main/java/com/databricks/jdbc/common/util/ValidationUtil.java @@ -20,6 +20,7 @@ public class ValidationUtil { private static final JdbcLogger LOGGER = JdbcLoggerFactory.getLogger(ValidationUtil.class); + public static void checkIfNonNegative(T number, String fieldName) throws DatabricksValidationException { if (number.longValue() < 0) {