← Oracle ACE articles

ACE-U field notes

JDBC/UCP to Oracle over TLS or mTLS with wallets

My baseline for connecting a Java application to Autonomous Database or another Oracle service securely.

Shubham Anandani··3 min read

Problem

A Java application needs to talk to a database, and the connection should be encrypted, should check it is talking to the right server, and should not carry a password in the source. Those three requirements are simple to state and easy to get partly wrong.

I wanted one baseline that I can adjust upward to mutual TLS when a policy asks for it, without relying on settings that change from driver version to driver version.

Options and trade-offs

  • Plain TCP with a username and password: simplest, with no transport protection and no server identity check.
  • Server-authenticated TLS: protects traffic and verifies the server, with fewer files to handle. Many services allow it.
  • Mutual TLS with a wallet: the client proves itself too. You need the wallet and a safe way to deliver it.

My recommendation

Default to TLS, and use mTLS with a wallet when policy requires client authentication. For Autonomous Database, use the TNS alias from the wallet's tnsnames.ora. Keep the password out of code by injecting it at start-up, and keep the pool conservative.

Worked example

With a wallet, point the driver at the directory that holds it and use the alias as the connection target. The ojdbc.properties file in the wallet supplies the wallet location.

// TNS_ADMIN is set outside the code (environment or -Doracle.net.tns_admin)
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
pds.setURL("jdbc:oracle:thin:@dbname_high");   // alias from tnsnames.ora
pds.setUser(System.getenv("DB_USER"));
pds.setPassword(System.getenv("DB_PASSWORD"));  // injected, not hard-coded
pds.setMinPoolSize(2);
pds.setMaxPoolSize(10);
pds.setValidateConnectionOnBorrow(true);

Then prove that the connection really is encrypted, instead of assuming it:

try (var c = pds.getConnection();
     var s = c.createStatement();
     var rs = s.executeQuery(
        "SELECT sys_context('USERENV','NETWORK_PROTOCOL') FROM dual")) {
    rs.next();
    System.out.println(rs.getString(1));   // expect: tcps
}

The aliases in a downloaded wallet normally include a server certificate name check. Leave it on. If you use an auto-login wallet file with an older driver, you may also need the Oracle PKI jars on the classpath, so check the driver documentation for the version you run.

Checklist

  1. Fetch the wallet securely, set TNS_ADMIN, and keep the files out of source control.
  2. Use UCP with modest minimum and maximum sizes, and validate on borrow.
  3. Choose TLS by default and mTLS only when required.
  4. Inject database credentials from a secret store at start-up.
  5. Keep the server certificate name check enabled.
  6. Log connection failures and test a failover before go-live.

Where this doesn't apply

Old drivers that lack the TLS features you need should be upgraded first. Databases from other vendors use different drivers and different security models.

One honest limitation

Wallets add operational work, such as delivery, rotation and cleanup. Treat the wallet like any other secret and automate its life cycle before the first rotation, not after.

Sources

  • Oracle JDBC Developer's Guide: Client-Side Security Features
  • Oracle Universal Connection Pool Developer's Guide: Setting Connection Pool Properties
  • Oracle Autonomous Database Documentation: Connect with JDBC Thin and a Wallet