MariaDB Connector/J
Last updated: 2026-08-10
MariaDB Connector/J is the official JDBC driver for MariaDB and MySQL — Maven coordinates org.mariadb.jdbc:mariadb-java-client, LGPL-licensed. This skill covers the connector-specific behavior and the traps that bite generated application code. For adding the driver to a build and configuring the URL, TLS, and pooling, see mariadb-connector-j-install. It is pure Java — it implements the client/server protocol itself and does not depend on MariaDB Connector/C (unlike Connector/Python or Connector/ODBC).
Default context: Assume the 3.5.x line (current release 3.5.10) unless the user states otherwise. Connector versions are independent of the MariaDB server version — a 3.x connector routinely talks to a 10.6/10.11/11.x server.
What LLMs Often Miss
| If the agent writes / assumes… |
…prefer the MariaDB form |
Class.forName("org.mariadb.jdbc.Driver") before connecting |
Not needed. The driver self-registers via the JDBC 4 META-INF/services/java.sql.Driver mechanism — DriverManager.getConnection(url) alone finds it. The legacy call still works, but only add it if targeting JDBC 3-era containers |
A jdbc:mysql://host:3306/db URL |
Use jdbc:mariadb://host:port/db?opts. The jdbc:mysql: prefix is only accepted when the permitMysqlScheme option is present in the URL — otherwise the driver won't claim it |
Builds SQL with string concat / String.format() / + |
Never. Use PreparedStatement with ? placeholders and setX(index, value) — the driver escapes/binds values, closing off SQL injection |
| Expects server-side prepared statements (binary protocol) by default |
useServerPrepStmts defaults to false. PreparedStatement parameter substitution happens client-side (text protocol) unless you set useServerPrepStmts=true on the URL. Enable it when the application reuses a bounded set of statements that stays within the prepare cache (prepStmtCacheSize, default 250) — repeated executions then reuse the server-side prepare, a substantial win. Leave it at the default when queries are mostly distinct: each fills a cache slot and consumes a server-side prepare (watch max_prepared_stmt_count) for no reuse benefit |
Loops single executeUpdate() calls for bulk insert/update |
Use addBatch() / executeBatch(). For INSERT batches, 3.x already sends them via the COM_STMT_BULK protocol by default (useBulkStmtsForInserts=true, useBulkStmts itself stays false for non-insert statements) — no extra option needed for the common case |
Reads getGeneratedKeys() after a batch insert expecting a single id |
getGeneratedKeys() returns all generated ids — one per inserted row — and the result is the same regardless of server. The driver only varies the mechanism: a fast bulk unit-results path on a MariaDB 11.5.1+ server (capability, MDEV-30366) and a pipelined fallback on older MariaDB or MySQL servers. Don't code for a single id |
Assumes autocommit is off; wraps every statement in setAutoCommit(false) needlessly, or forgets to call it |
autocommit defaults to true (standard JDBC, opposite of Connector/Python and Connector/C). For a transaction, call conn.setAutoCommit(false) once, then conn.commit() / conn.rollback() explicitly — don't rely on statement-level commit |
Streams a huge ResultSet with setFetchSize(Integer.MIN_VALUE) |
Throws SQLException — MariaDB Connector/J requires rows >= 0. Use a positive value, e.g. stmt.setFetchSize(1000), on a TYPE_FORWARD_ONLY statement to stream row-by-row. Even then, the fetch size is capped internally (16384) and the server still sends all rows to the client socket — running another statement on the same connection before the stream is fully read pulls the remainder into memory (OOM risk); use a separate connection for concurrent work |
Hand-rolls a pool, or opens a new Connection per request |
Use MariaDbPoolDataSource (internal pool: maxPoolSize default 8, minPoolSize defaults to maxPoolSize, maxIdleTime default 600s) or wrap the plain MariaDbDataSource/driver URL with an external pool (HikariCP). connection.close() on a pooled connection returns it to the pool — it doesn't disconnect — and resets autocommit, isolation level, and any active transaction (rolled back) |
| Builds a custom failover/round-robin loop over multiple hosts |
Multi-host URLs are native: jdbc:mariadb:sequential://host1,host2,host3/db (also replication:, loadbalance:, load-balance-read: prefixes) — comma-separate hosts in one URL instead of driver-side retry code |
Enables TLS with useSSL=true&verifyServerCertificate=true (other-driver naming) |
Since 3.0 the option is sslMode: disable (the default) / trust / verify-ca / verify-full. Certificate and key material is configuration — see mariadb-connector-j-install |
Calls a stored procedure with prepareStatement("CALL …") and reads OUT parameters from a result set |
Use CallableStatement from conn.prepareCall("{call proc(?, ?)}"), register OUT parameters with registerOutParameter(), and read them from the statement after execute() |
Assumes executeUpdate() returns a long for very large row counts |
It returns int. For counts beyond Integer.MAX_VALUE use executeLargeUpdate() / executeLargeBatch() |
Leaves an isolation level to a SET TRANSACTION statement |
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ) applies it through the driver, and a pooled connection resets it on release |
Catches a generic Exception or expects a MariaDB-specific exception class |
The driver throws the standard java.sql.SQLException hierarchy mapped from the server's SQLSTATE: SQLIntegrityConstraintViolationException, SQLSyntaxErrorException, SQLTransactionRollbackException, SQLTransientConnectionException, SQLNonTransientConnectionException. Read .getErrorCode() / .getSQLState() for the MariaDB-specific code, don't string-match .getMessage() |
Sets Statement.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS) and assumes it "just works" for any statement |
getGeneratedKeys() only returns rows when RETURN_GENERATED_KEYS was explicitly requested on that statement/prepareStatement() call — it's not implicit for a plain INSERT |
Connection, prepared statements, and a transaction
import java.sql.*;
// JDBC 4 auto-registers the driver — no Class.forName() needed
try (Connection conn = DriverManager.getConnection(
"jdbc:mariadb://localhost:3306/appdb", "app", "secret")) {
conn.setAutoCommit(false); // autocommit defaults to true — turn it off for a transaction
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO t (name, qty) VALUES (?, ?)")) {
ps.setString(1, "widget");
ps.setInt(2, 5);
ps.executeUpdate();
conn.commit();
} catch (SQLException e) {
conn.rollback();
throw e;
}
try (PreparedStatement ps = conn.prepareStatement(
"SELECT id, name FROM t WHERE qty > ?")) {
ps.setInt(1, 0);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getInt("id") + " " + rs.getString("name"));
}
}
}
}
Pooling with MariaDbPoolDataSource
import org.mariadb.jdbc.MariaDbPoolDataSource;
import java.sql.*;
try (MariaDbPoolDataSource pool = new MariaDbPoolDataSource(
"jdbc:mariadb://localhost:3306/appdb?user=app&password=secret&maxPoolSize=10")) {
try (Connection conn = pool.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT 1")) {
rs.next();
} // conn.close() (implicit) returns the connection to the pool, it doesn't disconnect
}
Batch insert and generated keys
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO t (name, qty) VALUES (?, ?)", Statement.RETURN_GENERATED_KEYS)) {
for (Row row : rows) {
ps.setString(1, row.name());
ps.setInt(2, row.qty());
ps.addBatch();
}
ps.executeBatch();
try (ResultSet keys = ps.getGeneratedKeys()) {
while (keys.next()) {
System.out.println(keys.getLong(1)); // one id per inserted row
}
}
}
RETURN_GENERATED_KEYS has to be requested when the statement is prepared; adding it later has no effect.
See Also
mariadb-connector-j-install — adding the driver to a Maven or Gradle build, the URL and datasource wiring, and TLS configuration
mariadb-connector-r2dbc-usage — the reactive, non-blocking sibling driver for Java (also pure Java, no Connector/C dependency)
mariadb-transactions / mariadb-set-transaction — the server-side semantics behind conn.commit() / conn.rollback() and isolation levels
mariadb-prepare — server-side prepared statements, what useServerPrepStmts=true uses under the hood
- Canonical reference on
mariadb.com/docs, consult for edge cases not covered here: https://mariadb.com/docs/connectors/mariadb-connector-j
This page is: Copyright © 2026 MariaDB. All rights reserved.
1---2name: mariadb-connector-j-usage3description: "MariaDB-specific behavior of MariaDB Connector/J (the `org.mariadb.jdbc:mariadb-java-client` JDBC driver) for application code — that JDBC 4 auto-registers the driver so `Class.forName` is unneeded; that the URL scheme is `jdbc:mariadb://host:port/db?opts` (`jdbc:mysql:` needs `permitMysqlScheme`); that `useServerPrepStmts` defaults to false, so `PreparedStatement` substitutes parameters client-side; that batched INSERTs use the `COM_STMT_BULK` protocol by default and `getGeneratedKeys()` then returns all generated ids, one per row, not a single id; that `autocommit` defaults to true so transactions need `setAutoCommit(false)`; that `setFetchSize(Integer.MIN_VALUE)` throws (fetch size must be >= 0); that pooling should use `MariaDbPoolDataSource` or an external pool; and that failover uses multi-host URLs, not driver code. Use when writing or reviewing Java code that talks to MariaDB with the `mariadb-java-client` JDBC driver."4---56# MariaDB Connector/J78*Last updated: 2026-08-10*910MariaDB Connector/J is the official JDBC driver for MariaDB and MySQL — Maven coordinates `org.mariadb.jdbc:mariadb-java-client`, LGPL-licensed. This skill covers the connector-specific behavior and the traps that bite generated application code. For adding the driver to a build and configuring the URL, TLS, and pooling, see **`mariadb-connector-j-install`**. It is **pure Java** — it implements the client/server protocol itself and does **not** depend on MariaDB Connector/C (unlike Connector/Python or Connector/ODBC).1112> **Default context:** Assume the **3.5.x** line (current release `3.5.10`) unless the user states otherwise. Connector versions are independent of the MariaDB server version — a 3.x connector routinely talks to a 10.6/10.11/11.x server.1314## What LLMs Often Miss1516| If the agent writes / assumes… | …prefer the MariaDB form |17|---|---|18| `Class.forName("org.mariadb.jdbc.Driver")` before connecting | **Not needed.** The driver self-registers via the JDBC 4 `META-INF/services/java.sql.Driver` mechanism — `DriverManager.getConnection(url)` alone finds it. The legacy call still works, but only add it if targeting JDBC 3-era containers |19| A `jdbc:mysql://host:3306/db` URL | Use **`jdbc:mariadb://host:port/db?opts`**. The `jdbc:mysql:` prefix is only accepted when the `permitMysqlScheme` option is present in the URL — otherwise the driver won't claim it |20| Builds SQL with string concat / `String.format()` / `+` | Never. Use **`PreparedStatement`** with `?` placeholders and `setX(index, value)` — the driver escapes/binds values, closing off SQL injection |21| Expects server-side prepared statements (binary protocol) by default | **`useServerPrepStmts` defaults to `false`.** `PreparedStatement` parameter substitution happens **client-side** (text protocol) unless you set `useServerPrepStmts=true` on the URL. Enable it when the application reuses a bounded set of statements that stays within the prepare cache (`prepStmtCacheSize`, default **250**) — repeated executions then reuse the server-side prepare, a substantial win. Leave it at the default when queries are mostly distinct: each fills a cache slot and consumes a server-side prepare (watch `max_prepared_stmt_count`) for no reuse benefit |22| Loops single `executeUpdate()` calls for bulk insert/update | Use **`addBatch()` / `executeBatch()`**. For `INSERT` batches, 3.x already sends them via the `COM_STMT_BULK` protocol by default (`useBulkStmtsForInserts=true`, `useBulkStmts` itself stays `false` for non-insert statements) — no extra option needed for the common case |23| Reads `getGeneratedKeys()` after a batch insert expecting a single id | `getGeneratedKeys()` returns **all** generated ids — one per inserted row — and the result is the **same regardless of server**. The driver only varies the mechanism: a fast bulk unit-results path on a **MariaDB 11.5.1+** server (capability, [MDEV-30366](https://jira.mariadb.org/browse/MDEV-30366)) and a **pipelined** fallback on older MariaDB or MySQL servers. Don't code for a single id |24| Assumes autocommit is off; wraps every statement in `setAutoCommit(false)` needlessly, or forgets to call it | **`autocommit` defaults to `true`** (standard JDBC, opposite of Connector/Python and Connector/C). For a transaction, call `conn.setAutoCommit(false)` once, then `conn.commit()` / `conn.rollback()` explicitly — don't rely on statement-level commit |25| Streams a huge `ResultSet` with `setFetchSize(Integer.MIN_VALUE)` | **Throws `SQLException`** — MariaDB Connector/J requires `rows >= 0`. Use a **positive** value, e.g. `stmt.setFetchSize(1000)`, on a `TYPE_FORWARD_ONLY` statement to stream row-by-row. Even then, the fetch size is capped internally (16384) and **the server still sends all rows** to the client socket — running another statement on the same connection before the stream is fully read pulls the remainder into memory (OOM risk); use a separate connection for concurrent work |26| Hand-rolls a pool, or opens a new `Connection` per request | Use **`MariaDbPoolDataSource`** (internal pool: `maxPoolSize` default **8**, `minPoolSize` defaults to `maxPoolSize`, `maxIdleTime` default **600s**) or wrap the plain `MariaDbDataSource`/driver URL with an external pool (HikariCP). `connection.close()` on a pooled connection returns it to the pool — it doesn't disconnect — and resets autocommit, isolation level, and any active transaction (rolled back) |27| Builds a custom failover/round-robin loop over multiple hosts | Multi-host URLs are native: **`jdbc:mariadb:sequential://host1,host2,host3/db`** (also `replication:`, `loadbalance:`, `load-balance-read:` prefixes) — comma-separate hosts in one URL instead of driver-side retry code |28| Enables TLS with `useSSL=true&verifyServerCertificate=true` (other-driver naming) | Since 3.0 the option is **`sslMode`**: `disable` (the default) / `trust` / `verify-ca` / `verify-full`. Certificate and key material is configuration — see **`mariadb-connector-j-install`** |29| Calls a stored procedure with `prepareStatement("CALL …")` and reads `OUT` parameters from a result set | Use **`CallableStatement`** from `conn.prepareCall("{call proc(?, ?)}")`, register `OUT` parameters with `registerOutParameter()`, and read them from the statement after `execute()` |30| Assumes `executeUpdate()` returns a `long` for very large row counts | It returns `int`. For counts beyond `Integer.MAX_VALUE` use **`executeLargeUpdate()`** / **`executeLargeBatch()`** |31| Leaves an isolation level to a `SET TRANSACTION` statement | `conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ)` applies it through the driver, and a pooled connection resets it on release |32| Catches a generic `Exception` or expects a MariaDB-specific exception class | The driver throws the **standard `java.sql.SQLException` hierarchy** mapped from the server's SQLSTATE: `SQLIntegrityConstraintViolationException`, `SQLSyntaxErrorException`, `SQLTransactionRollbackException`, `SQLTransientConnectionException`, `SQLNonTransientConnectionException`. Read `.getErrorCode()` / `.getSQLState()` for the MariaDB-specific code, don't string-match `.getMessage()` |33| Sets `Statement.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS)` and assumes it "just works" for any statement | `getGeneratedKeys()` only returns rows when `RETURN_GENERATED_KEYS` was explicitly requested on that statement/`prepareStatement()` call — it's not implicit for a plain `INSERT` |3435## Connection, prepared statements, and a transaction3637```java38import java.sql.*;3940// JDBC 4 auto-registers the driver — no Class.forName() needed41try (Connection conn = DriverManager.getConnection(42 "jdbc:mariadb://localhost:3306/appdb", "app", "secret")) {4344 conn.setAutoCommit(false); // autocommit defaults to true — turn it off for a transaction4546 try (PreparedStatement ps = conn.prepareStatement(47 "INSERT INTO t (name, qty) VALUES (?, ?)")) {48 ps.setString(1, "widget");49 ps.setInt(2, 5);50 ps.executeUpdate();51 conn.commit();52 } catch (SQLException e) {53 conn.rollback();54 throw e;55 }5657 try (PreparedStatement ps = conn.prepareStatement(58 "SELECT id, name FROM t WHERE qty > ?")) {59 ps.setInt(1, 0);60 try (ResultSet rs = ps.executeQuery()) {61 while (rs.next()) {62 System.out.println(rs.getInt("id") + " " + rs.getString("name"));63 }64 }65 }66}67```6869## Pooling with `MariaDbPoolDataSource`7071```java72import org.mariadb.jdbc.MariaDbPoolDataSource;73import java.sql.*;7475try (MariaDbPoolDataSource pool = new MariaDbPoolDataSource(76 "jdbc:mariadb://localhost:3306/appdb?user=app&password=secret&maxPoolSize=10")) {7778 try (Connection conn = pool.getConnection();79 Statement stmt = conn.createStatement();80 ResultSet rs = stmt.executeQuery("SELECT 1")) {81 rs.next();82 } // conn.close() (implicit) returns the connection to the pool, it doesn't disconnect83}84```8586## Batch insert and generated keys8788```java89try (PreparedStatement ps = conn.prepareStatement(90 "INSERT INTO t (name, qty) VALUES (?, ?)", Statement.RETURN_GENERATED_KEYS)) {91 for (Row row : rows) {92 ps.setString(1, row.name());93 ps.setInt(2, row.qty());94 ps.addBatch();95 }96 ps.executeBatch();9798 try (ResultSet keys = ps.getGeneratedKeys()) {99 while (keys.next()) {100 System.out.println(keys.getLong(1)); // one id per inserted row101 }102 }103}104```105106`RETURN_GENERATED_KEYS` has to be requested when the statement is prepared; adding it later has no effect.107108## See Also109110- **`mariadb-connector-j-install`** — adding the driver to a Maven or Gradle build, the URL and datasource wiring, and TLS configuration111- **`mariadb-connector-r2dbc-usage`** — the reactive, non-blocking sibling driver for Java (also pure Java, no Connector/C dependency)112- **`mariadb-transactions`** / **`mariadb-set-transaction`** — the server-side semantics behind `conn.commit()` / `conn.rollback()` and isolation levels113- **`mariadb-prepare`** — server-side prepared statements, what `useServerPrepStmts=true` uses under the hood114- Canonical reference on `mariadb.com/docs`, consult for edge cases not covered here: <https://mariadb.com/docs/connectors/mariadb-connector-j>115116<sub>_This page is: Copyright © 2026 MariaDB. All rights reserved._</sub>