flowable/flowable-engine · error · java.sql.SQLException

Cannot unwrap as an instance of

Error message

Cannot unwrap ${getClass().getName()} as an instance of ${iface.getName()}

What it means

TenantAwareDataSource.unwrap() only supports unwrapping to the TenantAwareDataSource class itself; it does not delegate to the underlying per-tenant DataSource. If a caller requests any other interface, it throws SQLException. This is a deliberate (minimal) JDBC Wrapper implementation.

Solutions

  1. Unwrap via isWrapperFor check only for TenantAwareDataSource.class, or obtain the underlying DataSource directly from the tenant map instead of unwrapping
  2. Extend/wrap TenantAwareDataSource with a delegating unwrap() that forwards to the current tenant's DataSource if you need vendor interfaces
  3. Check the object with isWrapperFor(iface) first and branch your code accordingly
  4. Avoid vendor-specific calls through the tenant-aware layer; use the raw DataSource per tenant for those operations

Example fix

// before
OracleDataSource ods = tenantAwareDs.unwrap(OracleDataSource.class);
// after
DataSource current = tenantAwareDs.getDataSource(tenantInfoHolder.getCurrentTenantId());
if (current.isWrapperFor(OracleDataSource.class)) {
    OracleDataSource ods = current.unwrap(OracleDataSource.class);
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!tenantAwareDs.isWrapperFor(iface)) {
    throw new SQLException(iface + " not supported by TenantAwareDataSource");
}

Type guard

boolean isTenantAwareDs(DataSource ds) {
    return ds instanceof TenantAwareDataSource;
}
// only unwrap TenantAwareDataSource.class from it

Try / catch

try {
    T unwrapped = ds.unwrap(iface);
} catch (SQLException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Cannot unwrap")) {
        // fall back to the underlying per-tenant DataSource
    }
}

Prevention

When it happens

Trigger: Calling unwrap(SomeInterface.class) on a TenantAwareDataSource where SomeInterface is not the TenantAwareDataSource type — e.g. vendor-specific interfaces like OracleDataSource, pool-specific types, or JDBC 4 Wrapper lookups by connection-pool tooling.

Common situations: Framework code (connection pool probes, monitoring, spring DataSource wrappers) probing for vendor extensions; code expecting unwrap to pass through to the underlying tenant DataSource; migrating code that previously used a native DataSource directly.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/69ec316e25d69a54. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/cfg/multitenant/TenantAwareDataSource.java:89

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0; // Default
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        if (iface.isInstance(this)) {
            return (T) this;
        }
        throw new SQLException("Cannot unwrap " + getClass().getName() + " as an instance of " + iface.getName());
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return iface.isInstance(this);
    }

    public Map<Object, DataSource> getDataSources() {
        return dataSources;
    }

    public void setDataSources(Map<Object, DataSource> dataSources) {
        this.dataSources = dataSources;
    }

    // Unsupported //////////////////////////////////////////////////////////

    @Override

View on GitHub (pinned to d6d39ce1c6)