How to Connect to SQL Server from Java

8 min readSQL Server

Java connects to Microsoft SQL Server through one driver worth using: the Microsoft JDBC Driver for SQL Server, published on Maven as com.microsoft.sqlserver:mssql-jdbc. It is a Type 4 (pure Java) driver maintained by Microsoft. There is no second serious contender the way there are two MySQL drivers; the jTDS project that older tutorials reference has not had a release since 2013 and does not support modern TLS or authentication, so skip it. This guide covers the dependency, the connection URL, the one default that trips up almost everyone on a fresh setup, pooling, and the errors you will meet.

The one driver you need

mssql-jdbc ships in two flavors per release, distinguished by a classifier in the version string: jre8 for Java 8 and jre11 for Java 11 and later. Pick the one matching your runtime. As of June 2026 the latest stable release is 13.4.0 (there are 13.5.0.jre*-preview builds on Maven Central, but those are previews, not GA). Driver 13.4 supports JDK 11, 17, 21, and 25.

Adding the dependency

Maven, on Java 11+:

<dependency>
  <groupId>com.microsoft.sqlserver</groupId>
  <artifactId>mssql-jdbc</artifactId>
  <version>13.4.0.jre11</version>
</dependency>

Gradle:

implementation 'com.microsoft.sqlserver:mssql-jdbc:13.4.0.jre11'

On Java 8, use 13.4.0.jre8 instead. The driver registers itself automatically through the JDBC service-provider mechanism, so you do not need Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver") on any modern JDK. Old tutorials still show that line; it is harmless but unnecessary.

The basic connection

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
 
String url = "jdbc:sqlserver://localhost:1433;"
        + "databaseName=AppDb;"
        + "encrypt=true;trustServerCertificate=true;"
        + "user=app;password=" + System.getenv("DB_PASSWORD");
 
try (Connection conn = DriverManager.getConnection(url)) {
    String sql = "SELECT id, email FROM dbo.users WHERE active = ?";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
        ps.setBoolean(1, true);
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                System.out.println(rs.getInt("id") + " " + rs.getString("email"));
            }
        }
    }
}

Two things to note. SQL Server uses ? positional placeholders, like the standard JDBC and unlike Oracle's named binds; never concatenate user input into the SQL string. And keep the password out of source: read it from an environment variable or a secrets manager, not a literal.

Connection URL format

The general shape is:

jdbc:sqlserver://[host][:port][;property=value;...]

The default port is 1433. Common properties:

PropertyWhat it does
databaseNameDatabase to connect to (otherwise the login's default DB)
encryptTLS for the connection. Default true since driver 10.x
trustServerCertificateSkip certificate validation. Convenient locally, unsafe in production
user / passwordSQL authentication credentials
integratedSecurityUse Windows/Kerberos auth instead of user+password
instanceNameConnect to a named instance (see below)
loginTimeoutSeconds to wait for a connection before failing

You can pass these as URL properties or via a java.util.Properties object handed to DriverManager.getConnection(url, props). The Properties form keeps the password out of any URL that might get logged.

The encrypt default that breaks local setups

This is the single most common first-connection failure, so it deserves its own section. Starting with driver version 10.x, encrypt defaults to true. That means the driver insists on a TLS-encrypted connection and, by default, validates the server's certificate. A typical local SQL Server or a fresh Docker container presents a self-signed certificate, so validation fails with:

The driver could not establish a secure connection to SQL Server by using
Secure Sockets Layer (SSL) encryption. ... PKIX path building failed

You have two honest options. For local development, add trustServerCertificate=true, which keeps the traffic encrypted but skips the certificate check. Do not ship that to production: it leaves you open to a man-in-the-middle. For production, leave trustServerCertificate=false and make the JVM trust the server's certificate properly, either by importing the server's CA into a Java truststore and pointing at it with trustStore and trustStorePassword, or by using a certificate from a CA the JVM already trusts (as Azure SQL Database does, where encryption just works with no extra flags).

Setting encrypt=false to make the error go away is the wrong reflex. Some servers are configured to force encryption and will reject the connection anyway, and you have now disabled transport security on the ones that would have accepted it.

Use a connection pool (HikariCP)

Opening a connection to SQL Server involves a TCP handshake, a TLS handshake, and a login round trip. Doing that per query is slow. In any service, pool connections. HikariCP (com.zaxxer:HikariCP, 7.1.0 as of June 2026) is the standard choice.

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
 
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:sqlserver://localhost:1433;databaseName=AppDb;"
        + "encrypt=true;trustServerCertificate=true");
config.setUsername("app");
config.setPassword(System.getenv("DB_PASSWORD"));
config.setMaximumPoolSize(10);
 
HikariDataSource ds = new HikariDataSource(config);
 
// Per request: borrow, use, return (try-with-resources returns it to the pool)
try (Connection conn = ds.getConnection();
     PreparedStatement ps = conn.prepareStatement("SELECT COUNT(*) FROM dbo.orders")) {
    try (ResultSet rs = ps.executeQuery()) {
        if (rs.next()) System.out.println(rs.getInt(1));
    }
}

Closing a pooled connection returns it to the pool rather than tearing down the socket. Size the pool deliberately: the number of pool slots multiplied by the number of application instances must stay under what the SQL Server edition and configuration allow, and a small pool (often 10 or fewer per instance) usually outperforms a large one because it reduces lock and scheduler contention on the server. Build the HikariDataSource once at startup and share it.

Named instances

SQL Server lets you run multiple instances on one host, addressed by name rather than a fixed port (MACHINE\SQLEXPRESS is the classic example). Two ways to connect:

jdbc:sqlserver://dbhost;instanceName=SQLEXPRESS;databaseName=AppDb;encrypt=true;trustServerCertificate=true

When you use instanceName without a port, the driver asks the SQL Server Browser service over UDP port 1434 which TCP port that instance is listening on, then connects to it. If UDP 1434 is blocked by a firewall, or the Browser service is not running, resolution fails and the connection times out for reasons that look unrelated to networking. The robust alternative is to find the instance's actual TCP port and connect to it directly with host:port, skipping the Browser entirely.

Integrated (Windows / Kerberos) authentication

If you authenticate with a domain account rather than a SQL login, set integratedSecurity=true and drop the user/password:

jdbc:sqlserver://dbhost;databaseName=AppDb;integratedSecurity=true;encrypt=true

On a pure-Java/cross-platform deployment this uses Kerberos, which means the JVM needs a valid ticket and a correctly configured krb5.conf. On Windows you can instead let the driver use the native mssql-jdbc_auth DLL that ships with the download. Kerberos setup is the part people underestimate; if it is misconfigured you get Integrated authentication failed with little detail, and the fix is almost always in the Kerberos configuration, not the JDBC URL.

Transactions

JDBC connections start in autocommit mode, committing every statement on its own. For multi-statement units of work, turn autocommit off and commit or roll back explicitly:

try (Connection conn = ds.getConnection()) {
    conn.setAutoCommit(false);
    try (PreparedStatement debit = conn.prepareStatement(
             "UPDATE dbo.accounts SET balance = balance - ? WHERE id = ?");
         PreparedStatement credit = conn.prepareStatement(
             "UPDATE dbo.accounts SET balance = balance + ? WHERE id = ?")) {
        debit.setBigDecimal(1, amount); debit.setInt(2, fromId); debit.executeUpdate();
        credit.setBigDecimal(1, amount); credit.setInt(2, toId); credit.executeUpdate();
        conn.commit();
    } catch (Exception e) {
        conn.rollback();
        throw e;
    }
}

If you borrowed the connection from a pool, reset setAutoCommit(true) before returning it, or configure the pool to do so, otherwise the next borrower inherits autocommit-off and may leave a transaction open.

Getting generated keys

SQL Server has IDENTITY columns rather than RETURNING. To read the key a row got on insert, request generated keys:

String sql = "INSERT INTO dbo.users (email) VALUES (?)";
try (PreparedStatement ps = conn.prepareStatement(sql, java.sql.Statement.RETURN_GENERATED_KEYS)) {
    ps.setString(1, "new@example.com");
    ps.executeUpdate();
    try (ResultSet keys = ps.getGeneratedKeys()) {
        if (keys.next()) System.out.println("new id = " + keys.getLong(1));
    }
}

Errors you will actually hit

ErrorCause
PKIX path building failed / could not establish a secure connection ... SSLencrypt=true (the default) is validating a self-signed cert. Add trustServerCertificate=true for local dev, or trust the cert properly for production
Login failed for user '...'Wrong credentials, the login is disabled, or the server is set to Windows-only auth while you send a SQL login
The TCP/IP connection to the host ... has failedServer not listening on that host/port, firewall, or TCP/IP disabled in SQL Server Configuration Manager
Connection hangs then times out on a named instanceSQL Server Browser (UDP 1434) is blocked or not running; connect by explicit host:port instead
Cannot open database "X" requested by the logindatabaseName does not exist or the login has no access to it
Integrated authentication failedKerberos/krb5.conf misconfiguration, missing ticket, or (on Windows) the native auth DLL is not on the path

A note on querying after you connect

Once the connection works, most of your time goes into writing and checking queries, not maintaining JDBC boilerplate. Mako connects to SQL Server with AI-powered autocomplete so you can explore the schema and prototype a query interactively before moving it into your Java code. It is a read/query tool, not a schema-management or administration console, so it complements SQL Server Management Studio rather than replacing it. For bulk loading data, see our import CSV to SQL Server guide, and for a broader look at desktop options see the best SQL Server GUI clients.

Mako connects to SQL Server, PostgreSQL, MySQL, and more with AI-powered autocomplete. Try it free at mako.ai.

Mako — open source

Skip the terminal. Use Mako.

Connect your database, write queries with AI assistance, and import/export data in clicks. Free to start.