Introduction

Snowflake is a cloud-based data platform that enables simple, fast, and scalable data storage, processing, and analysis. Unlike traditional solutions, Snowflake is not built on existing databases or Hadoop; instead uses its own SQL query engine and a unique architecture designed specifically for the cloud. It is used for analytical databases, processing large volumes of data, and building so-called “Data Cloud” solutions that connect data from various sources. The platform automatically scales resources based on workload, optimizing both performance and cost. Thanks to its support for standard SQL and compatibility with various analytics and visualization tools, Snowflake is suitable for a wide range of users, from data analysts to engineers and data scientists.

In the following sections, we will focus on the technical aspect of integrating the Snowflake platform with the Java programming language, demonstrating how to establish a connection and work with data within Snowflake using Java.


Snowflake Java Database Connectivity (JDBC)

Snowflake Java Database Connectivity is the most commonly used method for integrating Snowflake with the Java programming language. It provides a standardized way to establish a connection, execute SQL queries, and manage data from within a Java application.

Advantages and Disadvantages of Using Snowflake JDBC

Using the Snowflake JDBC connection in Java offers key advantages. JDBC is a standard API that simplifies development and integrates well with existing Java tools. Snowflake’s official JDBC driver is regularly updated and allows direct SQL execution, transaction handling, and asynchronous queries, making it efficient for large data operations.

However, this approach has some limitations. It requires manual management of connections and asynchronous logic, which can add complexity. JDBC also lacks native support for certain advanced Snowflake features and may involve extra overhead with driver version management in larger systems.

Extending the JDBC Contract with Snowflake-Specific Interfaces

While the standard JDBC API (java.sql) provides a well-defined contract for executing SQL statements and managing result sets, Snowflake extends this contract through vendor-specific interfaces that expose additional features tailored to its cloud data platform capabilities.

These Snowflake-specific extensions are accessible by unwrapping the standard JDBC interfaces, such as Statement and Connection, into SnowflakeStatement and SnowflakeConnection. This unwrapping allows developers to leverage advanced functionalities not present in the core JDBC specification.

Some of these features, some of which will be covered later, include:

  • Asynchronous Query Execution: Using methods like executeAsyncQuery(), queries can be submitted without blocking the client thread, enabling improved application responsiveness and parallel processing.
  • Query Identification and Tracking: Methods such as getQueryID() return a unique identifier for each query, allowing applications to track, monitor, or cancel queries as needed.
  • Polling Query Status: Through functions like getQueryStatus(), clients can programmatically check the state of a running query, facilitating custom retry or timeout mechanisms.
  • Session and Connection Metadata Access: SnowflakeConnection exposes session-level information, including session parameters, current warehouse, role, and database context, which are critical for auditing and dynamic query behavior.
  • Retrieving Query Profile and Telemetry: Snowflake provides access to detailed query execution profiles, enabling performance tuning and diagnostics directly from the client side.
  • Support for Query Cancellation and Abort: Advanced control allows clients to cancel running queries through the JDBC driver interface, an important feature for resource management in long-running or user-interrupted operations.
  • File Transfer and Staging Utilities: Through extended connection interfaces, Snowflake JDBC supports operations for managing file uploads/downloads to internal or external stages, simplifying ETL workflows.

By leveraging these vendor-specific extensions, developers can build richer integrations with Snowflake’s platform, improving both performance and control over query execution beyond the limitations of standard JDBC.

Establishing a Connection to Snowflake Using JDBC

There are several different methods for establishing a connection to Snowflake, such as using Single Sign-On (SSO) for authentication, leveraging OAuth 2.0 authorization, and employing key pair authentication, among others. In this section, we will focus specifically on using key pair authentication to connect to Snowflake through JDBC securely. This approach allows applications to authenticate without relying on usernames and passwords, instead using public-private key cryptography to enhance security and support automated, credential-free access.

Below is a simple example of establishing this type of connection:

 

public static Connection getConnection() throws SQLException {
        
String url = "jdbc:snowflake://<account_identifier>.snowflakecomputing.com";

        Properties props = new Properties();
        props.put("user", "<user>");
        props.put("private_key_file", "/tmp/rsa_key.p8");
        props.put("private_key_file_pwd", "dummyPassword");
        props.put("db", "<database_name>");
        props.put("schema", "<schema_name>");
        props.put("warehouse", "<warehouse_name>");
        props.put("role", "<role_name>");

        return DriverManager.getConnection(url, props);
}


Snowflake Async JDBC Execution

Snowflake enables asynchronous execution of SQL queries via the JDBC driver, which is particularly useful for long-running queries that should not block the main application thread. Instead of waiting for the query to complete, the application can immediately receive a queryId and then check the status in the background, fetching the results once the query has finished.

The most commonly used asynchronous approach typically consists of the following three steps:

  1. Executing the Query and Retrieving the queryId 
  2. Checking the Query Execution Status
  3. Fetching the Results Once the Query Has Completed

In the following sections, we will provide a more detailed explanation of each step in this approach.


Executing the Query and Retrieving the queryId

The first step in asynchronous query execution is submitting the SQL statement to Snowflake without waiting for the full result set. This method instantly returns a unique queryId, which serves as a reference to track the progress of the query and retrieve its results once completed. 

Below is an example function that executes a query asynchronously and returns the corresponding queryId:

public String executeAsyncQuery(final String query) throws SQLException {
    try (Connection conn = getConnection();
         Statement stmt = conn.createStatement()) {
             SnowflakeStatement sfStatement =  stmt.unwrap(SnowflakeStatement.class);
             sfStatement.executeAsyncQuery(query);
             String queryId = sfStatement.getQueryID();

             return queryId;
     } catch (SQLException e) {
             throw e;
     }
}



Comparing JDBC Synchronous and Snowflake Asynchronous Execution

When working with relational databases through JDBC, the standard approach to executing SQL queries is synchronous. In this model, the thread initiating the query blocks until the database returns the full result set. While this approach is straightforward and works well for short-running queries, it can become a bottleneck when handling complex or long-running operations.

In contrast, Snowflake offers asynchronous query execution, allowing the client to submit a SQL statement and immediately continue with other tasks without waiting for the result. This is particularly useful for improving responsiveness in applications or for executing multiple queries in parallel.

Having shown the asynchronous execution example above, the following demonstrates how the same query would be executed synchronously using standard JDBC:

public void executeSyncQuery(final String query) throws SQLException {
    try (Connection conn = getConnection();
         Statement stmt = conn.createStatement();
         ResultSet rs = stmt.executeQuery(query)) {
        while (rs.next()) {
            // Process each row
            System.out.println(rs.getString(1));
        }
    } catch (SQLException e) {
        throw e;
    }
}


Checking the Query Execution Status

After obtaining the queryId from the previous step, we can use the SnowflakeConnection.createResultSet(queryId) method to retrieve the query status using the Snowflake Java connector directly. This method returns a ResultSet object, which can be unwrapped into a SnowflakeResultSet, allowing us to access the query status. This approach provides a simple and efficient way to track the state of an asynchronously executed query.

The function below implements the previously described logic, using the queryId to retrieve the query status directly via the Snowflake Java connector.

public QueryStatus getQueryStatus(final String queryId) throws SQLException {
    try (Connection conn = getConnection()) {
        SnowflakeConnection sfConnection = conn.unwrap(SnowflakeConnection.class);
        QueryStatus sfStatus;

      try (ResultSet rs = sfConnection.createResultSet(queryId)) {
     sfStatus =   rs.unwrap(SnowflakeResultSet.class).getStatus();
      }

        return sfStatus;
    }
}



Fetching the Results Once the Query Has Completed

This is the final step in the asynchronous query execution flow. Once the query has been submitted and its status successfully monitored, we can proceed to fetch the results, provided the query has completed successfully.

In this step, we’ll reuse the previously defined getQueryStatus function to check the status of the query. If the status is SUCCEEDED, we will then retrieve the actual result set using SnowflakeConnection.createResultSet(queryId).

public ResultSet fetchResults(final String queryId) throws SQLException {
    QueryStatus status = getQueryStatus(queryId);

    if (status == QueryStatus.SUCCEEDED) {
        Connection conn = getConnection();
        SnowflakeConnection sfConnection = conn.unwrap(SnowflakeConnection.class);
        return sfConnection.createResultSet(queryId);
    } else {
        throw new IllegalStateException("Query has not completed successfully. Current status: " + status);
    }
}


Persistence and Retention of Asynchronous Query Results

After handling all the previous steps, a natural question arises: How long can we retrieve the results of a given query?

Results of asynchronously executed queries in Snowflake are retained for up to 24 hours after execution. Once this retention period expires, it is no longer possible to fetch the results using the queryId via the fetchResults() method.

To ensure longer-term availability, it is recommended to explicitly save the query results into a permanent table immediately after the query finishes. This guarantees that the data remains accessible beyond the default 24-hour window and can be used for further analysis or processing as needed.


“Integrating Snowflake with Java: JDBC and Async JDBC Execution” Tech Bite was brought to you by Almedin Pašalić, Junior Software Engineer at Atlantbh.

Tech Bites are tips, tricks, snippets or explanations about various programming technologies and paradigms, which can help engineers with their everyday job.

 

Leave a comment

Your email address will not be published. Required fields are marked *