Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring + lombok + @SneakyThrows

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
like image 801
Volodymyr Roman Avatar asked Oct 30 '17 14:10

Volodymyr Roman


People also ask

What is @SneakyThrows in spring boot?

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.

Can we use throw without throws Java?

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.

Can we use throws at class level?

You can by throw exception at constructor level.


1 Answers

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

like image 169
tsarenkotxt Avatar answered Oct 12 '22 08:10

tsarenkotxt