JDBC MCP Server
MCP server (stdio) that exposes read-only access to relational databases over JDBC: schema
metadata, SELECT execution, execution plans, statistics and index analysis. Built-in dialects
for PostgreSQL, Oracle, SQL Server, Firebird and SQLite (drivers bundled); other databases are
served through DatabaseMetaData with an external driver.
Contents: Features · Typical Scenarios · Quickstart · Architecture · MCP Tools · MCP Resources · Read-only Protection · Server Environment Variables · Build · Stack · Troubleshooting · License
Features
- Engines: PostgreSQL 11+, Oracle 12c+, SQL Server 2012+, Firebird 3+, SQLite; generic JDBC
via
driverPath. Capabilities per engine: Supported Databases. - Write protection: JSqlParser AST guard (single
SELECT/WITH/EXPLAIN), plus session-, transaction- or file-level read-only mode where the engine supports it. Details: Read-only Protection. - Connections: any number of databases in one
connections.json; every tool takes aconnectionargument; pools are created on first use. Credentials are not read from the environment. - Tools: 49, in 11 groups that can be disabled individually: metadata, query execution, plan analysis, column distribution and selectivity, table and index statistics, schema context, benchmarks, usage catalog. Reference: MCP Tools.
- Local catalog: per-connection SQLite file with a persistent structure snapshot and an index of known application queries (usage catalog).
- Clients: any MCP client with stdio transport; configuration examples for Claude Code, Codex CLI, OpenCode, VS Code, Copilot CLI, Cursor, Claude Desktop and Qwen Code in docs/clients.md.
Typical Scenarios
The agent picks the tools itself; these are the chains it usually follows. Every tool is described in MCP Tools.
Quickstart
1. Get the jar — download jdbc-mcp-server.jar from the
latest release (JDK 21+ required;
all JDBC drivers are bundled), or build it yourself:
./gradlew bootJar # → build/libs/jdbc-mcp-server.jar
No local JDK? Use the Docker image ghcr.io/igorolv/jdbc-mcp-server instead — see
Docker.
2. Describe your databases in ~/.jdbc-mcp-server/connections.json
(%USERPROFILE%\.jdbc-mcp-server\connections.json on Windows, /data/connections.json in the
Docker image) — one entry per database, keyed by the connection name:
{
"connections": {
"orders": {
"url": "jdbc:postgresql://db.example.com:5432/orders",
"username": "ai_readonly",
"password": "secret",
"defaultSchema": "public",
"description": "Order service — customers, orders, shipments",
"structureSnapshotSchemas": ["public", "nsi"]
},
"billing": {
"url": "jdbc:oracle:thin:@//oracle.example.com:1521/BILLING",
"username": "AI_READONLY",
"password": "${BILLING_DB_PASSWORD}",
"defaultSchema": "BILLING_OWNER",
"description": "Legacy billing (Oracle)"
},
"crm": {
"url": "jdbc:sqlserver://sql.example.com:1433;databaseName=crm;encrypt=true",
"username": "ai_readonly",
"password": "secret",
"defaultSchema": "dbo",
"description": "CRM (SQL Server)"
},
"legacy": {
"url": "jdbc:firebirdsql://fb.example.com:3050//var/lib/firebird/data/app.fdb",
"username": "SYSDBA",
"password": "secret",
"description": "Pre-2010 warehouse app (Firebird)"
},
"archive": {
"url": "jdbc:firebirdsql:embedded:/srv/data/archive-copy.fdb?nativeLibraryPath=/opt/firebird/lib",
"username": "SYSDBA",
"password": "secret",
"description": "Copy of the warehouse archive, opened in-process (Firebird embedded)"
},
"analytics": {
"url": "jdbc:sqlite:/srv/data/analytics.db",
"description": "Nightly analytics extract (SQLite)"
},
"shop": {
"url": "jdbc:mysql://mysql.example.com:3306/shop",
"username": "ai_readonly",
"password": "secret",
"driverPath": "drivers/mysql-connector-j-9.1.0.jar",
"description": "Web shop (MySQL, generic JDBC)"
}
}
}
urlis the only required field; the engine is detected from its prefix. Any other URL needs adriverPathto a driver jar and is served as generic JDBC.descriptionis returned bylistConnections, so an agent picks a database by meaning; name the stand and any restriction ("PRODUCTION — keep queries small").archiveopens a local Firebird file in-process:nativeLibraryPathis the directory holdinglibfbclient.so/fbclient.dllwith its engine and plugins. Point it at a copy of the file — see Firebird.- Use a read-only database user: it is the only protection that does not depend on this server. Keep the file readable only by its owner.
Every field, naming rules, ${VAR} secrets and several stands of one service:
docs/connections.md.
3. Register the server with your MCP client — with no database settings in the client config:
{
"command": "java",
"args": ["-jar", "<absolute-path>/jdbc-mcp-server.jar"],
"env": {}
}
For Claude Code and Codex CLI that is one command:
claude mcp add --scope user jdbc -- java -jar /path/to/jdbc-mcp-server.jar
codex mcp add jdbc -- java -jar /path/to/jdbc-mcp-server.jar
OpenCode, VS Code with Copilot, Copilot CLI, Cursor, Claude Desktop, Qwen Code, and tips that apply to every client: docs/clients.md.
4. Try it. Ask the agent in plain words:
- "Which databases can you reach?" — it calls
listConnectionsand lists the entries of yourconnections.jsonwith their descriptions. - "How many tables are there in
orders?" — it callslistTableson that connection and counts them.
If the first call against a database fails, see Checking a configuration.
Architecture
MCP client (Claude Code, Codex, VS Code, Cursor, ...)
| JSON-RPC over stdin / stdout, no network port
v
+- jdbc-mcp-server: one JVM, started by the client ------------------------------+
| |
| MCP layer 49 tools in switchable groups, optional resources |
| | every call names its `connection` |
| v |
| Connection registry <-- connections.json, read once at startup |
| | a connection is built on its first call, then reused |
| v |
| Per-connection context |
| +- read-only guard (JSqlParser AST) -> SQL executor (row cap, timeout) |
| +- dialect: PostgreSQL, Oracle, SQL Server, Firebird, SQLite, generic |
| +- metadata, statistics, plan parser and analyzer |
| +- local catalog <data-dir>/<name>/<name>.db (SQLite, WAL): |
| | structure snapshot + usage index of known queries |
| +- Hikari pool of read-only JDBC connections -----------------> database |
+--------------------------------------------------------------------------------+
- One process, many databases. The client starts the server as a child process and talks to it
over stdio. The databases come from
connections.json; a connection's pool, local catalog and services are created only when a tool call first names it, so a database that is down or misconfigured affects only the calls made against it. - The SQL path. Every statement goes through the read-only guard first, then runs on a read-only JDBC connection with the connection's row cap and timeout. Engine-level protections are described in Read-only Protection.
- Dialects. Each engine has its own implementation of metadata queries, plans and statistics;
any other database is served in generic mode through
DatabaseMetaDataand portable SQL — see Supported Databases. - Local catalog. Structural metadata is persisted per connection in a SQLite file with no
expiry:
describeTable, the schema context tools andsearchObjectsread covered schemas from it and fall back to the live database otherwise. The same file holds the usage index. Live statistics, samples and plans are never cached. See Persistent Structure Snapshot and Usage Catalog. - Execution model. Tool calls on one stdio session run sequentially. The MCP Java SDK 2.0.0 used
by Spring AI 2.0.1 can lose responses when several concurrently executed tools finish at the same
time, so the server keeps
immediateExecution(true)until the SDK fixes this. A client'snotifications/cancelledis not propagated to JDBCStatement.cancel(); the configuredqueryTimeoutSeconds(or a tool call'stimeoutSecondsoverride) remains the server-side limit for a running SQL statement.
MCP Tools
The 49 tools are grouped below by purpose.
Every tool takes connection as its first, required argument, naming the database to run
against — including installations that serve exactly one database. listConnections lists the
names. See Several databases in one server.
Tool Groups
Tools are organised into groups that can be turned on or off independently with
JDBC_MCP_TOOLS_* flags. All groups are on by default, so the full tool set is available out of
the box. Turning groups off shrinks the tools/list manifest, which matters for small-context
(local) models that would otherwise be flooded with tool schemas before the first call.
Each flag accepts true / false. For a small-context local model, turn off the groups you do not
need — for example keep only Metadata + Query by setting the rest to false — to cut the manifest
down to a minimal "explore the schema and run a query" set. The sections below describe each tool
regardless of its group.
Query
Benchmarking
Tools for measuring the real cost of a query, so the LLM does not have to guess from the plan and can see actual milliseconds and buffer counters.
Metadata
Schema Context
High-level tools for quick schema orientation and SQL authoring. Instead of manually calling
listTables -> describeTable -> sampleRows for each table, an LLM can get ready-to-use
context in one call: tables, columns, relationships, constraints, and sample rows.
Edge evidence
When includeObserved is left unset, tableContext / findJoinPaths enable
it automatically if the local usage catalog is enabled (see Usage Catalog below). Every
relationship edge then carries a typed three-layer evidence bundle. Each layer is independently
optional and is omitted when there is no signal:
declaredSchema— the relationship is a declared foreign key in the database catalog. Carries the FK name and column lists.observedQuery— the equi-join pair appears in stored application queries. CarriesjoinSupport(number of distinct queries) andqueryUids(up to 5 contributing uids).semanticUsage— terms shared across queries that touch both tables: business domains, business objects, and output labels, plus the co-occurring query count and uid preview. This layer decorates existing edges only — it never proposes new relationships.
{
"relationshipType": "foreignKey",
"fromTable": "ORDERS", "fromColumns": ["CUSTOMER_ID"],
"toTable": "CUSTOMERS", "toColumns": ["ID"],
"evidence": {
"declaredSchema": { "foreignKeyName": "FK_ORDERS_CUSTOMER", "fromColumns": ["CUSTOMER_ID"], "toColumns": ["ID"] },
"observedQuery": { "joinSupport": 18, "queryUids": ["SHOP/InvoiceReport.json#header"] },
"semanticUsage": {
"sharedBusinessDomains": [{ "value": "Customers", "support": 12, "queryUids": [...] }],
"sharedBusinessObjects": [{ "value": "Invoice payer", "support": 4, "queryUids": [...] }],
"sharedOutputLabels": [{ "value": "Payer name", "support": 3, "queryUids": [...] }],
"coOccurringQueryCount": 22,
"coOccurringQueryUids": [...]
}
}
}
Equi-join pairs seen only in stored queries (no declared FK) are appended as new edges with
relationshipType: "observed" and undirected: true, between tables already in scope. Composite
(multi-column) FKs receive a declaredSchema layer but no observed-pair match in this iteration.
schemaBrief, schemaGraph, and queryContext only surface declared FK relationships.
Evidence model
The schema-context layer keeps three sources of knowledge separate:
declared_schema- live database introspection: tables, columns, PK/FK, indexes, constraints, comments and statistics.observed_query- the indexed query catalog: which stored application/report queries reference a table or column, and in which SQL context (select,where,join,order_by,having).semantic_usage- adapter-supplied business meaning: query domains/tags/labels, output labels, parameter descriptions, field usages, rendered business objects and confidence.
In tableContext, the existing table fields are the compact declared_schema view. When
includeObserved is enabled and the usage catalog is available, each table also gets an
evidence block:
{
"evidence": {
"observedQuery": {
"queryCount": 12,
"queryUids": ["SHOP/reports/customer-card#main"],
"columns": [
{"column": "STATUS", "queryCount": 5, "contexts": [{"value": "where", "support": 4}]}
]
},
"semanticUsage": {
"businessDomains": [{"value": "Customers", "support": 8}],
"businessTags": [{"value": "customer", "support": 6}],
"queryLabels": [{"value": "Customer card", "support": 3}],
"outputLabels": [{"value": "Customer name", "support": 4}],
"businessObjects": [{"value": "Customer card", "support": 3}]
}
}
}
The server treats this as evidence, not as a single canonical business model. Different queries may legitimately attach different business roles to the same physical table or column.
queryContext also uses semantic_usage as a discovery signal. When the user passes natural
language terms, the server searches usage-catalog domains, tags, query labels, output labels and
business objects. Matching tables are returned in semanticMatches and are considered before the
fallback name/comment scan over live schema metadata. This lets terms such as "payer" find a
physical CUSTOMERS table when existing reports expose customers.name as "Payer name".
Usage Catalog
A catalog of known SQL usage against the inspected database, together with optional business context: parameters with descriptions, output columns with their meaning, and where each output is displayed in the consuming artifact (Excel cell in a BI Publisher report, dashboard widget, etc.).
There are two sources. File-backed usage comes from directories / JSON files / zip archives containing canonical QueryUsage JSON records. Database-native usage is derived automatically from the connected schema's views, routines and triggers. At runtime the server parses these records and builds a persistent SQLite index with extracted tables / columns / equi-join pairs as facts. JSON files remain authoritative for file-backed records; native records are refreshed from live metadata.
Purpose. The metadata tools answer "what tables and columns exist"; the usage catalog answers
"how are they used by applications". It supports lookups such as "which reports reference this
column" and "which business label does this output field carry", and it feeds the observedQuery
and semanticUsage layers of the relationship evidence bundle, so equi-joins seen in stored
queries appear next to declared foreign keys (for example, "these two columns are joined in 17
stored report queries", with their uids).
Identity. Each query is keyed by (source.kind, source.path, source.unit). Diagnostics and
evidence render this key as:
{source.kind}/{source.path}#{source.unit}
The #unit suffix is omitted when there is no unit. Examples:
bi-publisher-report/reports/customers/CustomerCard.xdo#CUST
manual/manual/ad-hoc-2026-05-01
java-dao/src/main/java/com/example/shop/OrderDao.java#findByCustomer
source.kind and source.unit must not contain / or #; source.path must not contain #.
For duplicate source keys, the first record wins for that index build.
Where the files live. The default catalog directory is
<data-dir>/<connection>/usage-catalog. Configure usageCatalogPaths on the connection as a list
of additional directories, .json files, or .zip archives. Directories are scanned recursively
for *.json; zip archives are scanned for JSON entries. Set usageCatalogEnabled: false to
disable the catalog. usageCatalogStatus then reports catalogEnabled: false; other public usage
tools return an argument error explaining how to enable it.
Database-native usage. The catalog also indexes supported database objects from the default schema:
- views / materialized views as
source.kind="database-view"orsource.kind="database-materialized-view"; - functions and procedures as
source.kind="database-function"/source.kind="database-procedure"where the engine reports that distinction; - triggers as
source.kind="database-trigger".
Views usually contribute fully parsed table, column and join evidence. Routine and trigger bodies
are engine-specific, so the indexer first uses an ANTLR-based procedural pre-extractor to find
embedded SELECT / WITH / INSERT / UPDATE / DELETE / MERGE statements, then feeds those
statements into the existing JSqlParser analysis pipeline. If no embedded statement is found, the
object is still kept as a provenance record. Use usageNativeSchemas on the connection to
scan explicit schemas.
Persistent index. The server never builds the usage index on startup. The first usage-catalog
lookup builds it synchronously from file-backed records and database-native objects into the local
SQLite <catalog>.db. Source files and database objects remain authoritative. Use
invalidateUsageCatalogCache after changing them; it clears the indexed usage rows and the next
lookup rebuilds them.
Local-only writes. The usage catalog never writes to the inspected JDBC database. The existing ReadOnlyGuard and connection-level protections
remain in force.
Typed payload. The canonical source, parameters[], outputs[], fieldUsages[] and nested
objects are described by the JSON Schema (field names, types, descriptions, enum values). The same
record types (QueryUsage and friends in usage/format/) are used by file indexing.
The canonical source-agnostic JSON format is documented in
docs/usage-catalog-format.md; its JSON Schema lives at
src/main/resources/schemas/query-usage-record.schema.json, with examples under
examples/usage/. Source-specific adapters should emit this canonical shape rather than being
implemented inside the JDBC MCP server.
Resolution. During indexing, table / column qualifiers are resolved cheaply through the
parser's alias map and uppercased for case-insensitive matching. An explicit schema in the SQL
(SCHEMA.TABLE) is preserved verbatim. Unqualified table references are resolved as part of the
index build against the live JDBC schema: exactly one match fills the schema, multiple matches are
marked ambiguous, and zero matches stay unresolved.
Catalog Administration
This tool writes only to the local catalog. It does not modify the inspected database.
Connections
listConnections reads configuration and the local filesystem only — it opens no database
connection, so it still answers when some of the configured databases are down. In an unfamiliar
installation it is the first call worth making.
Persistent Structure Snapshot
Structural metadata (columns, keys, indexes, FKs, views, routines, triggers, sequences) is held in a
persistent structure snapshot stored in the local SQLite <catalog>.db file (the same database
file as the usage catalog, under <data-dir>/<catalog>/). SQLite runs in WAL mode, so Codex,
Claude, and other local agent processes can use the same catalog concurrently. This speeds up repeated calls to
tableContext, findJoinPaths, schemaLint, schemaGraph, queryContext, describeTable,
searchObjects, and the usage-catalog re-resolver. Statistics tools such as tableStats,
indexStats, columnStats, and sampleRows are not cached; their counters are live.
The snapshot is authoritative ("cache forever") — there is no TTL or staleness detection. It is
filled lazily (describeTable persists each table it loads) and can be front-loaded for whole
schemas with the rebuildCatalog tool, which builds the structure snapshot and the usage index
into one distributable <catalog>.db. rebuildCatalog checkpoints the WAL before returning.
Clear the catalog while all server processes are stopped by deleting <catalog>.db and any
adjacent <catalog>.db-wal / <catalog>.db-shm files.
Existing H2 <catalog>.mv.db files are not converted or deleted. On first SQLite startup the
server creates a new <catalog>.db, logs a warning, and leaves the legacy file untouched; run
rebuildCatalog to populate the new catalog.
Configuration:
structureSnapshotSchemas- schemas to front-load on a full rebuild (empty → the default schema).structureSnapshotOracleColumnQueryTimeoutSeconds- Oracle-only timeout for theDBMS_XMLGEN-backed bulk column/default query during a full rebuild (default300;0disables).
Both are per-connection fields in connections.json.
Data Exploration
Selectivity and Distribution
columnStats only reports extremes. The other tools answer "how selective is this predicate?"
and "how skewed are values in this column?", which is the information an LLM needs to choose an
index or rewrite a JOIN meaningfully.
Object Statistics
These tools give the LLM object scale and health signals; without that, optimization advice becomes
guesswork. Data comes from system catalogs (pg_class, pg_stat_*, ALL_TABLES, ALL_INDEXES,
DBA_SEGMENTS) and is aggregated on the Java side.
All tools are read-only; data is not modified.
MCP Resources
Besides tools, the server can publish the catalog as MCP resources, for clients that let the user
attach a table's description to the context. Resources are off by default; set
JDBC_MCP_RESOURCES_ENABLED=true to register them. The tools are the same either way.
Every usable connection publishes one resource and two resource templates:
<catalog>is the connection name, UTF-8 percent-encoded (ssj@devbecomesssj%40dev). It is fixed per connection, not a template argument: a read resolves its connection from the URI, so a URI never points at the wrong database — across the connections of one server or across several registered instances. Schema, table and column segments are percent-encoded and keep their case.- Tables are not listed.
resources/listholds only the manifests; a schema with thousands of tables would otherwise turn it into a dump of the catalog. Clients discover names throughcompletion/completeon the template arguments —schema, thentable(givenschema), thencolumn(given both) — with case-insensitive prefix matches, at most 100 per response andhasMorewhen there are more. - Completions come from the local catalog and never touch the database. Until a connection has
a catalog they return nothing and the manifest reports snapshot version
0; a catalog built withrebuildCatalogis picked up without a restart. Table and column reads work either way: likedescribeTable, they answer from the snapshot and fall back to the live database. - Metadata. Every read carries
_metawithcatalog,resourceSchemaVersion,snapshotVersionand, once a catalog exists,snapshotBuiltAt. - Errors use MCP error codes: a table or column that does not exist is
-32002(resource not found,data.urinames the URI); a malformed URI is-32602(invalid params).
Read-only Protection
The server never writes to the inspected database, and does not rely on one mechanism for that:
- a JSqlParser AST guard lets through a single
SELECT,WITHorEXPLAINand nothing else; - every JDBC connection is read-only, and PostgreSQL, Firebird and SQLite enforce it inside the
database (a read-only session or transaction, a file opened with
open_mode=1); - database credentials are kept in
connections.json, never in the MCP client config or the environment, so an agent has no easy way around the server withpsqlorsqlplus.
The strongest guarantee is a read-only database user. The layers per engine, GRANT snippets for
PostgreSQL, Oracle and SQL Server, when the guard can be switched off, and why credentials live in a
file: docs/read-only.md. Tool errors, including the guard's rejected, are
listed in docs/errors.md.
Server Environment Variables
Databases, credentials and everything that varies per database live in
connections.json — deliberately
not in the environment. The
environment configures only the server process itself:
A connection's own settings — URL, credentials, default schema, timeouts, row caps, pool sizes, the
read-only guard, snapshot and usage options — are fields of its connections.json entry; see
Connection fields.
Build
# Set JDK 21+ explicitly if it is not your default JDK:
export JAVA_HOME="$HOME/.jdks/jdk-21.0.6"
./gradlew build
Result: build/libs/jdbc-mcp-server.jar (includes PostgreSQL, Oracle, SQL Server, Firebird, and SQLite drivers).
Stack
- Java 21, Spring Boot 4.0, Spring AI MCP 2.0.1 (
stdiotransport) - HikariCP through Spring Boot
starter-jdbc - PostgreSQL JDBC 42.7.4
- Oracle JDBC
ojdbc1123.6.0.24.10 - Microsoft SQL Server JDBC 12.8.1
- Firebird JDBC (Jaybird) 6.0.6
- SQLite 3.51.3 (
sqlite-jdbc): the WAL catalog (<catalog>.db) holding the usage index and persistent structure snapshot, and the driver for SQLite connections - Gradle 9.3.1 with version catalog
Troubleshooting
- "Cannot find a Java installation ... matching languageVersion=21" - install JDK 21+ and set
JAVA_HOME. Gradle toolchains cannot download it without internet access. - Connection refused / ORA-01017 / FATAL / SQL Server login failed - check the connection's
url,username, andpasswordinconnections.json. For PostgreSQL, test the URL withpsql; for Oracle, usesqlplus user/password@...; for SQL Server, test withsqlcmd -S host,1433 -d database -U user -P password. {"kind":"rejected","error":"Only SELECT / WITH / EXPLAIN statements are allowed"}- the guard worked. This is expected for any write operation. If the query is truly read-only, for example a read-only function call throughSELECT func(...), it will pass. For fully non-trivial cases, you can disable the guard with"readonlyGuard": "off"on that connection.- Oracle write attempt reached the database - this should normally be blocked by the guard first. If
readonlyGuardisoff, rely on a read-only Oracle user; JDBCsetReadOnly(true)is only a best-effort hint for Oracle. - Empty
describeTable/listTablesresult on Oracle - Oracle stores object names in uppercase. PassCUSTOMERS, notcustomers. - Generic JDBC: "No driver in ... accepts the URL" - the jars in
driverPathregister no driver for that URL prefix. Check the URL, or name the class withdriverClass. - Generic JDBC:
kind: "unsupported"- the tool needs something JDBC does not expose portably (plans, view sources, sequences). See Generic JDBC for what works. - Firebird:
not_foundfromdescribeTable, orargumenterror "Firebird has no schemas" - Firebird stores unquoted names in uppercase and has no schemas: passCUSTOMERSand omitschema(or passPUBLIC). - SQLite: "unable to open database file" - the path in the URL does not exist (the read-only open never creates a file) or is not readable. Use an absolute path; on Windows forward slashes work:
jdbc:sqlite:C:/data/app.db. - Firebird: "unsupported on-disk structure" - the server version does not match the file's ODS (Firebird 3 reads ODS 12 only, Firebird 4/5 read ODS 13). Serve the file with the matching Firebird version, or back it up with
gbakand restore it on a newer one. - SQL Server certificate errors - set the JDBC URL encryption options explicitly, for example
encrypt=true;trustServerCertificate=falsewith a trusted certificate, ortrustServerCertificate=trueonly for local/dev use. - SQL Server
unusedIndexesunsupported - this tool intentionally avoidssys.dm_db_index_usage_statsbecause it usually requires elevated state-view permissions. UseindexStats,fkIndexCoverage, andredundantIndexesfor low-privilege SQL Server audits. - The MCP client says the server failed to start - run
java -jar jdbc-mcp-server.jar < /dev/nullin a terminal; a malformedconnections.json, an invalid connection name or an unset${VAR}is reported there. See Checking the configuration. - A setting in
connections.jsonhas no effect - unknown keys are ignored without a warning, so check the spelling against Connection fields. The file is read only at startup: restart or reconnect the server after editing it. - A connection shows
configErrorinlistConnections- the entry is unusable (unsupported URL withoutdriverPath, unknowndialect, missing driver jar); the other connections keep working. Configuration errors lists every message.
License
This project is licensed under the Apache License, Version 2.0. See LICENSE.
Runtime and test dependencies are licensed by their respective owners. See
THIRD_PARTY_NOTICES.md, especially if you distribute a built fat jar containing bundled JDBC
drivers.