mybatis/mybatis-3 · warning · SQLException
${className} is not a wrapper.
Error message
${className} is not a wrapper. What it means
UnpooledDataSource and PooledDataSource implement the java.sql.Wrapper interface but do not wrap anything, so unwrap(Class) always throws SQLException '<ClassName> is not a wrapper.' and isWrapperFor returns false. Wrapper.unwrap is meant to let callers extract a vendor-specific implementation from a proxy; these DataSource implementations are plain, so there is nothing to unwrap.
Source
Thrown at src/main/java/org/apache/ibatis/datasource/pooled/PooledDataSource.java:631
public static Connection unwrapConnection(Connection conn) {
if (Proxy.isProxyClass(conn.getClass())) {
InvocationHandler handler = Proxy.getInvocationHandler(conn);
if (handler instanceof PooledConnection) {
return ((PooledConnection) handler).getRealConnection();
}
}
return conn;
}
@Override
protected void finalize() throws Throwable {
forceCloseAll();
super.finalize();
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
throw new SQLException(getClass().getName() + " is not a wrapper.");
}
@Override
public boolean isWrapperFor(Class<?> iface) {
return false;
}
@Override
public Logger getParentLogger() {
return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
}
}
View on GitHub (pinned to 008069adb1)
Solutions
- Guard with isWrapperFor(iface) before calling unwrap() — it returns false for these classes
- To get vendor/driver-specific objects, unwrap the java.sql.Connection obtained from getConnection(), not the DataSource
- If you need a pool with unwrap support (JMX metrics, etc.), use an external pool (HikariCP, DBCP) and plug it into MyBatis via a custom DataSourceFactory or env-provided DataSource
Example fix
// before
HikariDataSource h = dataSource.unwrap(HikariDataSource.class); // SQLException
// after
if (dataSource.isWrapperFor(HikariDataSource.class)) {
HikariDataSource h = dataSource.unwrap(HikariDataSource.class);
} else {
// MyBatis POOLED/UNPOOLED DataSource: nothing to unwrap
} Defensive patterns
Strategy: type-guard
Validate before calling
if (dataSource.isWrapperFor(HikariDataSource.class)) { // false for MyBatis pools
HikariDataSource h = dataSource.unwrap(HikariDataSource.class);
} Type guard
static boolean isUnwrappable(javax.sql.DataSource ds, Class<?> iface) {
return ds != null && ds.isWrapperFor(iface); // MyBatis Pooled/Unpooled return false
} Try / catch
try {
return dataSource.unwrap(iface);
} catch (SQLException e) {
if (e.getMessage().endsWith("is not a wrapper.")) return null; // not a proxy
throw e;
} Prevention
- Always pair unwrap() with an isWrapperFor() check
- Unwrap Connections, not DataSources, for driver-level access
- Prefer instanceof checks on the concrete DataSource class you expect
When it happens
Trigger: Calling pooledDataSource.unwrap(SomeClass.class) on the MyBatis DataSource itself; generic framework code (monitoring, metrics, de-correlators) that probes DataSources via unwrap(); attempting unwrap(Connection.class) or a driver class against the DataSource rather than against a Connection obtained from it.
Common situations: Integration libraries that try to detect HikariCP/Tomcat/uwm-specific datasources by unwrapping them; migration from a wrapping pool (HikariCP) to MyBatis PooledDataSource where code unwrapped HikariDataSource; trying to access driver-level APIs through the DataSource.
Related errors
- ${className} is not a wrapper.
- There was an error configuring JndiDataSourceTransactionPool
- Unknown DataSource property: ${propertyName}
- Parameter 'dataSource' must not be null
- dataSource cannot be null
AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14).
Data as JSON: /api/errors/559c9734a8335926.
Report an issue: GitHub.