4

In a webapp implemented as a Jetty container, we have a custom javax.ws.rs.ext.MessageBodyWriter<T> annotated with

@Singleton
@Provider
@Produces( "application/rss+xml" )

We also have a resource which works just fine. The get() method is annotated with

@Produces( "application/vnd.api+json" )

Visiting that endpoint returns the expected json response.

Adding .rss to the endpoint causes a 406 response to be returned.

What could be the reason it is not finding the MessageBodyWriter for returning the RSS response?

The full stacktrace is:

javax.ws.rs.NotAcceptableException: HTTP 406 Not Acceptable
    at org.glassfish.jersey.server.internal.routing.MethodSelectingRouter.getMethodRouter(MethodSelectingRouter.java:472)
    at org.glassfish.jersey.server.internal.routing.MethodSelectingRouter.access$000(MethodSelectingRouter.java:73)
    at org.glassfish.jersey.server.internal.routing.MethodSelectingRouter$4.apply(MethodSelectingRouter.java:674)
    at org.glassfish.jersey.server.internal.routing.MethodSelectingRouter.apply(MethodSelectingRouter.java:305)
    at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:86)
    at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:89)
    at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:89)
    at org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:69)
    at org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:38)
    at org.glassfish.jersey.process.internal.Stages.process(Stages.java:173)
    at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:247)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:234)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:684)
    at <our package>.Jetty94HttpContainer.handle(Jetty94HttpContainer.java:167) // Jetty94HttpContainer extends AbstractHandler implements Container
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
    at com.codahale.metrics.jetty9.InstrumentedHandler.handle(InstrumentedHandler.java:284)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
    at org.eclipse.jetty.server.Server.handle(Server.java:516)
    at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:487)
    at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:732)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:479)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
    at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:338)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:315)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:173)
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131)
    at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:409)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034)
    at java.base/java.lang.Thread.run(Thread.java:829)
Stewart
  • 17,616
  • 8
  • 52
  • 80
  • 2
    1. How did you register the provider? 2. Did you set the accept header? 3. I think there is a set of media types where "extensions" are supported. Rss may not be one of them, hence Accept header may be required. Not sure, I would need to dig into the source code to see which extensions are supported. – Paul Samsotha Aug 18 '22 at 14:59
  • @PaulSamsotha There is a file `mediatypes.conf` wherein is defined `rss: application/rss+xml` and this is apparently working because I can debug and see the `Accept` header is correct. – Stewart Aug 18 '22 at 20:12
  • @PaulSamsotha The `MessageBodyWriter` is registered, because I can prove its constructor is called during boot up. I'm going to dig in to whether `isWriteable()` method is being called ... – Stewart Aug 18 '22 at 20:13
  • 1
    What do you mean by "Adding .rss to the endpoint"? Sorry :P – DialFrost Aug 20 '22 at 23:58
  • @DialFrost So I have an endpoint which returns json, say, http://localhost:8080/app/endpoint and adding `.rss` to the end would be http://localhost:8080/app/endpoint.rss – Stewart Aug 22 '22 at 13:46
  • 1
    did your register the provider with your Jersey application, either implicitly through package scanning, or explicitly with the ResourceConfig? found [this](https://www.appsloveworld.com/springboot/100/234/jersey-produce-response-rss-feed-messagebodywriter-not-found-for-media-typeappl) that could help. – Lety Aug 27 '22 at 16:32
  • @Lety Thank you for this clue. I hadn't previously found the documentation despite looking. Currently I'm debugging through the bootup to see what I can about the registration. Inside `org.glassfish.jersey.message.internal.MessageBodyFactory` I can see the `MessageBodyWriter` is created and passed into the `addWriters()` method. From here I need to work out how it links up with the `@Produces` annotation – Stewart Sep 02 '22 at 20:01
  • 1
    did you have a class that extends org.glassfish.jersey.server.ResourceConfig and that register your class? or web.xml configuration in order to add your package to the list of scanned package? [this](https://stackoverflow.com/questions/10048004/integrating-jetty-with-jax-rs-jersey) could help – Lety Sep 03 '22 at 11:56
  • @Lety Yes, I have a `ResourceConfig` and the `MessageBodyWriter` is registered by package scanning. Thank you for this pointer. What I have found, debugging through registration process, is that it registers for `OPTIONS`, but not `GET` or `POST` requests. This is curious. – Stewart Sep 03 '22 at 12:48
  • 1
    did you try to add @GET? – Lety Sep 03 '22 at 13:21
  • @Lety This is the clue that got it working! The *Resource* needs the correct `@Produces` as well as the `MessageBodyWriter`! Sounds obvious, but I totally missed it. In other words, I changed the `get()` method of the Resource class to read `@Produces( {PRODUCES_JSON_API, CustomMediaType.APPLICATION_RSS_XML} )` and now it works. If you want to post an answer, I am happy to give you the bounty! – Stewart Sep 03 '22 at 13:37
  • I've moved the answer embedded in the question to a CW post - let me know if you would like to post it in your own name. Note: it looks like as part of your answer update, you amended the question with the fix that you needed. It is best if you can avoid doing that - readers will want to look at this question in the future and understand the problem that was had at the time, and how the answers below are solutions to that problem. Thus, it makes the answers redundant/confusing if there is no problem in the latest version of the question. – halfer Sep 05 '22 at 21:50
  • @halfer Understood on posting the answer as an answer, rather than update. My intention was to correctly award the bounty, so Lety s answer is the accepted one, and is essentially the same as mine. As for altering the question with the fix - this is NOT what I did at all. What I did was remove red herring code which was not part of the original problem. So I'm going to re-apply that edit. – Stewart Sep 06 '22 at 10:27

2 Answers2

1

The resource methods annotated with a request method should declare the supported request media types equals to the entity providers that supply mapping services between representations and their associated Java types.

Adding:

@Produces( "application/vnd.api+json", "application/rss+xml" )

to the get() method should do the trick.

Lety
  • 2,511
  • 21
  • 25
0

(Posted an answer from the question author in order to move it to the answer space).

Thanks to Lety I was able to find the problem. It's one of those which are super-obvious once you realise it, but which no amount of staring at the code alone would reveal!

This answer also helped

If you want to switch a resource output between different output types depending on the Accept request header, the resource method needs @Produces to cover all the desired output types, as well as having a MessageBodyWriter configured for that output.

In other words, I changed the get() method of the resource to read ...

@Produces( "application/vnd.api+json", "application/rss+xml" )

... and this is what made it work.

halfer
  • 19,824
  • 17
  • 99
  • 186