Luis Muñiz
Luis Muñiz

Reputation: 4811

Micronaut @Error annotation not working (micronaut 3.7.3)

I'm trying to improve the error handling in a controller by using the @Error annotation.

    @Get(uri = "/{id}")
    @Secured("ROLE_VIEW")
    HttpResponse<AuthorResource> show(Long id) {
        try {
            ok(authorService.get(id).get())
        } catch (Exception e) {
            e.printStackTrace()
            throw e
        }
    }

The get() method will either return the AuthorResource (io.vavr.control.Try), or throw a custom exception, subclass of our base ErrorContext exception class.

I then added the following errorhandler in the controller:

    @Error(exception = ErrorContext, global = true)
    HttpResponse<ErrorContext> onErrorContext(HttpRequest request, ErrorContext error) {
        HttpResponse.<ErrorContext>status(HttpStatus.valueOf(error.code)).body(error)
    }

Unfortunately, the exception handler does not seem to be invoked, and an INTERNAL_SERVER_ERROR is triggered (instead of a NOT_FOUND in this example). The output of the gradle test task can be found below.

I have tried the following:

Am I missing anything?

I have created a working application in github here

Controller class

Test class

Output of test (including partial stacktraces due to length limitation in SO):

10:50:31.455 [ForkJoinPool.commonPool-worker-7] INFO  i.m.t.e.TestResourcesResolverLoader - Loaded 1 test resources resolvers: io.micronaut.testresources.testcontainers.GenericTestContainerProvider
10:50:31.579 [main] INFO  i.m.testresources.server.Application - A Micronaut Test Resources server is listening on port 43207, started in 182ms
10:50:33.164 [default-nioEventLoopGroup-1-2] INFO  i.m.t.e.TestResourcesResolverLoader - Loaded 1 test resources resolvers: io.micronaut.testresources.testcontainers.GenericTestContainerProvider

> Task :test

AuthorControllerSpec STANDARD_OUT
    10:50:32.581 [Test worker] INFO  i.m.context.env.DefaultEnvironment - Established active environments: [test]
    10:50:33.131 [multithreadEventLoopGroup-1-1] DEBUG i.m.h.client.netty.DefaultHttpClient - Sending HTTP GET to http://localhost:43207/requirements/entries
    10:50:33.190 [multithreadEventLoopGroup-1-1] DEBUG i.m.h.client.netty.DefaultHttpClient - Received response 200 from http://localhost:43207/requirements/entries
    10:50:33.216 [multithreadEventLoopGroup-1-2] DEBUG i.m.h.client.netty.DefaultHttpClient - Sending HTTP POST to http://localhost:43207/list
    10:50:33.246 [multithreadEventLoopGroup-1-2] DEBUG i.m.h.client.netty.DefaultHttpClient - Received response 200 from http://localhost:43207/list
    10:50:33.249 [multithreadEventLoopGroup-1-3] DEBUG i.m.h.client.netty.DefaultHttpClient - Sending HTTP GET to http://localhost:43207/requirements/entries
    10:50:33.254 [multithreadEventLoopGroup-1-3] DEBUG i.m.h.client.netty.DefaultHttpClient - Received response 200 from http://localhost:43207/requirements/entries
    10:50:33.258 [multithreadEventLoopGroup-1-4] DEBUG i.m.h.client.netty.DefaultHttpClient - Sending HTTP POST to http://localhost:43207/list
    10:50:33.262 [multithreadEventLoopGroup-1-4] DEBUG i.m.h.client.netty.DefaultHttpClient - Received response 200 from http://localhost:43207/list
    10:50:33.367 [Test worker] INFO  i.m.c.h.g.HibernateDatastoreFactory - Starting GORM for Hibernate
    10:50:33.883 [Test worker] INFO  org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.11.Final
    10:50:33.984 [Test worker] INFO  o.h.validator.internal.util.Version - HV000001: Hibernate Validator 6.2.5.Final
    10:50:34.074 [Test worker] INFO  o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
    10:50:34.139 [Test worker] INFO  org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.H2Dialect

AuthorControllerSpec > It fails to get an non-existing Author > example.controller.AuthorControllerSpec.It fails to get an non-existing Author [badId: 546252431, #0] STANDARD_OUT
    10:50:34.879 [default-nioEventLoopGroup-3-2] DEBUG i.m.h.client.netty.DefaultHttpClient - Sending HTTP GET to http://localhost:37361/author/546252431

AuthorControllerSpec > It fails to get an non-existing Author > example.controller.AuthorControllerSpec.It fails to get an non-existing Author [badId: 546252431, #0] STANDARD_ERROR
    error.ErrorContextWithoutStacktrace: Author with id 546252431 not found.

AuthorControllerSpec > It fails to get an non-existing Author > example.controller.AuthorControllerSpec.It fails to get an non-existing Author [badId: 546252431, #0] STANDARD_OUT
    10:50:34.999 [default-nioEventLoopGroup-3-3] ERROR i.m.http.server.RouteExecutor - Unexpected error occurred: Required argument [HttpRequest request] not specified
    io.micronaut.web.router.exceptions.UnsatisfiedRouteException: Required argument [HttpRequest request] not specified
        at io.micronaut.web.router.exceptions.UnsatisfiedRouteException.create(UnsatisfiedRouteException.java:76)
        at io.micronaut.web.router.AbstractRouteMatch.execute(AbstractRouteMatch.java:297)
        at io.micronaut.web.router.RouteMatch.execute(RouteMatch.java:111)
        at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:103)
        at io.micronaut.http.server.RouteExecutor.lambda$executeRoute$14(RouteExecutor.java:659)
        at reactor.core.publisher.FluxDeferContextual.subscribe(FluxDeferContextual.java:49)
[Skipped a lot of reactive frames]
        at reactor.core.publisher.Flux.subscribe(Flux.java:8522)
        at io.micronaut.http.server.netty.RoutingInBoundHandler.handleRouteMatch(RoutingInBoundHandler.java:601)
        at io.micronaut.http.server.netty.RoutingInBoundHandler.channelRead0(RoutingInBoundHandler.java:457)
        at io.micronaut.http.server.netty.RoutingInBoundHandler.channelRead0(RoutingInBoundHandler.java:147)
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99)
[Skipped a lot of netty channel stuff]
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:833)
    10:50:35.031 [default-nioEventLoopGroup-3-2] DEBUG i.m.h.client.netty.DefaultHttpClient - Received response 500 from http://localhost:37361/author/546252431
    10:50:35.049 [Test worker] ERROR i.m.r.intercept.RecoveryInterceptor - Type [example.controller.Api$Intercepted] executed with error: error.ErrorResource@475fb7
    io.micronaut.http.client.exceptions.HttpClientResponseException: error.ErrorResource@475fb7
        at io.micronaut.http.client.netty.DefaultHttpClient$FullHttpResponseHandler.makeErrorFromRequestBody(DefaultHttpClient.java:2226)
        at io.micronaut.http.client.netty.DefaultHttpClient$FullHttpResponseHandler.buildResponse(DefaultHttpClient.java:2199)
        at io.micronaut.http.client.netty.DefaultHttpClient$FullHttpResponseHandler.buildResponse(DefaultHttpClient.java:2122)
        at io.micronaut.http.client.netty.DefaultHttpClient$BaseHttpResponseHandler.channelReadInstrumented(DefaultHttpClient.java:2097)
        at io.micronaut.http.client.netty.DefaultHttpClient$FullHttpResponseHandler.channelReadInstrumented(DefaultHttpClient.java:2158)
        at io.micronaut.http.client.netty.DefaultHttpClient$FullHttpResponseHandler.channelReadInstrumented(DefaultHttpClient.java:2122)
        at io.micronaut.http.client.netty.SimpleChannelInboundHandlerInstrumented.channelRead0(SimpleChannelInboundHandlerInstrumented.java:49)
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
[Skipped a lot of netty channel stuff]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:833)
        Suppressed: java.lang.Exception: #block terminated with an error
                at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:99)
                at reactor.core.publisher.Flux.blockFirst(Flux.java:2600)
                at io.micronaut.http.client.netty.DefaultHttpClient$1.exchange(DefaultHttpClient.java:498)
                at io.micronaut.http.client.netty.DefaultHttpClient$1.retrieve(DefaultHttpClient.java:505)
                at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.lambda$intercept$5(HttpClientIntroductionAdvice.java:409)
                at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.handleBlockingCall(HttpClientIntroductionAdvice.java:508)
                at io.micronaut.http.client.interceptor.HttpClientIntroductionAdvice.intercept(HttpClientIntroductionAdvice.java:408)
                at io.micronaut.aop.chain.MethodInterceptorChain.proceed(MethodInterceptorChain.java:137)
                at io.micronaut.retry.intercept.RecoveryInterceptor.intercept(RecoveryInterceptor.java:92)
                at io.micronaut.aop.chain.MethodInterceptorChain.proceed(MethodInterceptorChain.java:137)
                at example.controller.Api$Intercepted.getAuthor(Unknown Source)
                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
                at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
                at java.base/java.lang.reflect.Method.invoke(Method.java:568)
                at org.codehaus.groovy.runtime.callsite.PlainObjectMetaMethodSite.doInvoke(PlainObjectMetaMethodSite.java:43)
                at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrap.invoke(PojoMetaMethodSite.java:203)
                at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56)
                at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
                at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
                at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:148)
                at example.controller.AuthorControllerSpec.$spock_feature_0_0(AuthorControllerSpec.groovy:33)
                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
                at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
                at java.base/java.lang.reflect.Method.invoke(Method.java:568)
                at org.spockframework.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:198)
[Skipped a lot of gradle, junit, and spock stuff]
                at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

AuthorControllerSpec > It fails to get an non-existing Author > example.controller.AuthorControllerSpec.It fails to get an non-existing Author [badId: 546252431, #0] FAILED
    org.spockframework.runtime.SpockComparisonFailure at AuthorControllerSpec.groovy:37

Upvotes: 1

Views: 1411

Answers (1)

Иван Зыков
Иван Зыков

Reputation: 493

You imported the wrong HttpRequest

Instead of import java.net.http.HttpRequest you should import import io.micronaut.http.HttpRequest.

But even then you will not catch HttpClientResponseException because you don't use any Client in your test. If you want to receive this exception you should write something like this:

    @Inject
    @Client("/author")
    HttpClient client

    @IgnoreRest
    def "It fails to get an non-existing Author with an HttpClient"() {
        given:
        def token = viewer()

        when:
        def author = client.toBlocking().exchange(HttpRequest.create(
                HttpMethod.GET,
                "/" + badId
        ).bearerAuth(token))

        then:
        def ex = thrown(HttpClientResponseException)
        ex.status == NOT_FOUND
        ex.getResponse().getBody(ErrorResource).map {
            assert it.message == "Author with id ${badId} not found."
            it
        }.isPresent()

        where:
        badId = anyInt()
    }

Upvotes: 2

Related Questions