I'm using @SneakyThrows Lombok feature in my SpringBoot project.
I have problems with this feature when CGLIB proxies implementation
it throws
java.lang.Exception: Unexpected exception,
expected but was<java.lang.reflect.UndeclaredThrowableException>.
Can it be fixed somehow ?
Providing examples.
There is interface and two implementations.
public interface SneakyThrowsExample {
void testSneakyThrows();
}
Simple implementation
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component(value = "simpleSneakyThrowsExample")
public class SimpleSneakyThrowsExample implements SneakyThrowsExample {
@Override
@SneakyThrows
public void testSneakyThrows() {
throw new IOException();
}
}
And @Transactional implementations. CGLIB will proxy this implementation.
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
@Component(value = "transactionalSneakyThrowsExample")
public class TransactionalSneakyThrowsExample implements SneakyThrowsExample {
@Override
@SneakyThrows
@Transactional
public void testSneakyThrows() {
throw new IOException();
}
}
Create @SpringBootTest test and inject these 2 implementation
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.IOException;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DefaultSneakyThrowsExampleTest {
@Autowired
@Qualifier(value = "transactionalSneakyThrowsExample")
SneakyThrowsExample transactionalSneakyThrowsExample;
@Autowired
@Qualifier(value = "simpleSneakyThrowsExample")
SneakyThrowsExample simpleSneakyThrowsExample;
@Test(expected = IOException.class)
public void testSneakyThrowsSimple() throws Exception {
this.simpleSneakyThrowsExample.testSneakyThrows();
}
@Test(expected = IOException.class)
public void testSneakyThrowsTransactional() throws Exception {
this.transactionalSneakyThrowsExample.testSneakyThrows();
}
}
Test testSneakyThrowsTransactional fails with error
java.lang.Exception: Unexpected exception, expected<java.io.IOException> but was<java.lang.reflect.UndeclaredThrowableException>
at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:28)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.reflect.UndeclaredThrowableException
at fine.project.TransactionalSneakyThrowsExample$$EnhancerBySpringCGLIB$$57df642e.testSneakyThrows(<generated>)
at fine.project.DefaultSneakyThrowsExampleTest.testSneakyThrowsTransactional(DefaultSneakyThrowsExampleTest.java:35)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:19)
... 20 more
Caused by: java.io.IOException
at fine.project.TransactionalSneakyThrowsExample.testSneakyThrows(TransactionalSneakyThrowsExample.java:21)
at fine.project.TransactionalSneakyThrowsExample$$FastClassBySpringCGLIB$$e5429d83.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
... 31 more
The @SneakyThrows annotation from Lombok allows you to throw checked exceptions without using the throws declaration. This comes in handy when you need to raise an exception from a method within very restrictive interfaces like Runnable.
Without using throws When an exception is cached in a catch block, you can re-throw it using the throw keyword (which is used to throw the exception objects). If you re-throw the exception, just like in the case of throws clause this exception now, will be generated at in the method that calls the current one.
You can by throw exception at constructor level.
When you use @Transactional then Spring will create a proxy for your bean via AOP proxies - Spring Framework’s declarative transaction
UndeclaredThrowableException cause:
Thrown by a method invocation on a proxy instance if its invocation handler's invoke method throws a checked exception (a Throwable that is not assignable to RuntimeException or Error) that is not assignable to any of the exception types declared in the throws clause of the method that was invoked on the proxy instance and dispatched to the invocation handler.
Lombock @SneakyThrows:
Can be used to sneakily throw checked exceptions without actually declaring this in your method's throws clause
It means that your TransactionalSneakyThrowsExample.testSneakyThrows()
throws checked exception (that undeclared in the throws
in method signature) it's illegal behavior when instance wrapped in proxy
In this case you can change expected exception to the Exception.class
:
@Test(expected = Exception.class)
public void testSneakyThrowsTransactional() throws Exception {
this.transactionalSneakyThrowsExample.testSneakyThrows();
}
or you can use ExpectedException.expectCause()
to cheack IOException.class
in your test, take a look at JUnit expect a wrapped exception
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With