Skip to content

Unwrap the exceptions thrown by a DataSource bean proxied by QuickPerf - #294

Merged
jeanbisutti merged 1 commit into
quick-perf:masterfrom
jeanbisutti:fix-spring-datasource-proxy-exception-wrapping
Oct 20, 2026
Merged

jeanbisutti merged 1 commit into
quick-perf:masterfrom
jeanbisutti:fix-spring-datasource-proxy-exception-wrapping

Conversation

@jeanbisutti

Copy link
Copy Markdown
Collaborator

With QuickPerfProxyBeanPostProcessor, the caller of DataSource.getConnection() did not get the SQLException thrown by the DataSource bean. It got an UndeclaredThrowableException, wrapping an InvocationTargetException, wrapping the SQLException.

For example, with a DataSource bean whose database refuses the connection:

try (Connection connection = dataSource.getConnection()) {
    // ...
} catch (SQLException e) {
    // not executed before this PR
}

getConnection() threw the exception below, so the catch block did not run. This is the trace of the Spring Boot 2 test of this PR, run without the fix, with the other frames removed:

java.lang.reflect.UndeclaredThrowableException
	at org.quickperf.spring.springboottest.datasourceexception.SqlExceptionOfDataSource$DataSourceRefusingConnections$$EnhancerBySpringCGLIB$$bde0c3e9.getConnection(<generated>)
	...
Caused by: java.lang.reflect.InvocationTargetException
	...
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.quickperf.spring.sql.QuickPerfProxyBeanPostProcessor$ProxyDataSourceInterceptor.invoke(QuickPerfProxyBeanPostProcessor.java:157)
	...
Caused by: java.sql.SQLException: Connection refused
	at org.quickperf.spring.springboottest.datasourceexception.SqlExceptionOfDataSource$DataSourceRefusingConnections.getConnection(SqlExceptionOfDataSource.java:55)
	...

With this PR, getConnection() throws the SQLException: Connection refused itself, and the catch block runs.

The post-processor proxies each DataSource bean. Its ProxyDataSourceInterceptor calls the matching method of the QuickPerf datasource-proxy with Method.invoke, which wraps anything the method throws in an InvocationTargetException. The interceptor rethrew this checked exception as is. The method called, such as getConnection(), does not declare it, so Spring or the proxy wrapped it in an UndeclaredThrowableException. This happened with each Spring version tried (3.2, 4.3, 5.1, 6.1 and 6.2) with the CGLIB proxy of the DataSource bean, and, from Spring 6.1.2, with the JDK proxy that the post-processor creates for the read-only DataSource of a LazyConnectionDataSourceProxy.

Code catching SQLException did not see it. For example, JdbcTemplate threw this UndeclaredThrowableException instead of a CannotGetJdbcConnectionException caused by the SQLException, because DataSourceUtils.getConnection catches SQLException.

The other no-argument methods called through the interceptor, such as getLoginTimeout() and getParentLogger(), had the same problem.

@jeanbisutti
jeanbisutti merged commit 0fa0b7a into quick-perf:master Oct 20, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant