1

I'm using resteasy-jaxrs in combination with jackson-datatype-jsr310 to serialize LocalDateTime in the response. This works fine for properties in my classes because I can add the necessary annotation like

@XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
public LocalDateTime getExpirationDate() {
    return expirationDate;
}

using the LocalDateTimeAdapter

public class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {
    @Override
    public LocalDateTime unmarshal(String s) throws Exception {
        return LocalDateTime.parse(s);
    }

    @Override
    public String marshal(LocalDateTime dateTime) throws Exception {
        return dateTime.toString();
    }
}

In the json I get something like

"expirationDate": "2026-07-17T23:59:59"

But I've a map of objects and this map also can have items of type LocalDateTime and for those there is no annotation and so I get the full object in the json response like

"effectiveDate":       {
  "dayOfMonth": 1,
  "dayOfWeek": "FRIDAY",
  "month": "JUNE",
  "year": 2018, ...

Is there a way to format every LocalDateTime field, no matter where it comes from?

UPDATE after Paul's answer

Since I already hat the com.fasterxml.jackson.datatype:jackson-datatype-jsr310 dependency, I added the ObjectMapperContextResolver class and removed the annotation from my LocalDateTime property. Sadly all LocalDateTimes were now serialized as full object again. In the comments of the post I saw that someone added a context-param to the web.xml so that the ObjectMapperContextResolver gets picked up. After adding this to my web.xml, it looks like this.

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
    <display-name>Servlet 3.1 Web Application</display-name>
    <listener>
        <listener-class>com.kopi.web.InitListener</listener-class>
    </listener>
    <context-param>
        <param-name>resteasy.resources</param-name>
        <param-value>com.kopi.utils.ObjectMapperContextResolver</param-value>
    </context-param>
</web-app>

Now since adding the context-param resteasy.resources, the webapp doesn't start anymore due to

SEVERE: Servlet [com.kopi.rest.RestApplication] in web application [/kopi] threw load() exception
java.lang.RuntimeException: RESTEASY003130: Class is not a root resource.  It, or one of its interfaces must be annotated with @Path: com.kopi.utils.ObjectMapperContextResolver implements:  javax.ws.rs.ext.ContextResolver
    at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:179)
    at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:158)
    at org.jboss.resteasy.core.ResourceMethodRegistry.addPerRequestResource(ResourceMethodRegistry.java:77)
    at org.jboss.resteasy.spi.ResteasyDeployment.registration(ResteasyDeployment.java:482)
    at org.jboss.resteasy.spi.ResteasyDeployment.startInternal(ResteasyDeployment.java:279)
    at org.jboss.resteasy.spi.ResteasyDeployment.start(ResteasyDeployment.java:86)
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.init(ServletContainerDispatcher.java:119)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.init(HttpServletDispatcher.java:36)
    at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1194)
    at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1110)
    at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1000)
    at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4902)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5212)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:152)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:724)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:700)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:734)
    at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:596)
    at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1805)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

In other comments I saw that versions might be an issue. So I'm currently using

org.jboss.resteasy:resteasy-jaxrs 3.5.1.Final
org.jboss.resteasy:resteasy-jackson-provider 3.5.1.Final
org.jboss.resteasy:resteasy-servlet-initializer 3.5.1.Final
com.fasterxml.jackson.datatype:jackson-datatype-jsr310 2.9.5

Thank you, kopi

kopi_b
  • 1,453
  • 3
  • 16
  • 20

2 Answers2

0

Instead of the XmlAdapter which you need to declare on individual properties, you could just configure it globally with Jackson using a ContextResolver. This is where you can configure the ObjectMapper and register the JavaTimeModule with the mapper. This configuration will be global so you don't need to use the XmlAdapter. To see how you can configure this, you can see this post.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • 1
    Hello Paul, thanks for your hint. Unfortunately now I'm facing a different problem. Would you please look at my update. Thank you. – kopi_b May 18 '18 at 12:20
  • You have it listed under `resteasy.resources`. It's not a resource. If you annotate with `@Provider` it should be enough if you have classpath scanning enabled. Or else you need to manually register it programmatically. See https://stackoverflow.com/a/50357807/2587435 – Paul Samsotha May 18 '18 at 14:05
  • You can also use `resteasy.providers`. That should work. – Paul Samsotha May 18 '18 at 14:10
  • Hello Paul, sorry for my late response but I wasn't able to test your input until now. To make sure that nothing else interferes, I created a completely new project and using just your classes LocalDateResource, Person and ObjectMapperContextResolver from your other post. After adding the necessary javax.ws.rs.core.Application class with the ApplicationPath annotation, the constructor of the ObjectMapperContextResolver class is called during applicationstart and I can call the person endpoint. But the result is still the same :( Should the getMapper() be called at some point? – kopi_b May 30 '18 at 15:43
  • Is the `getContext()` method called during the request? – Paul Samsotha May 30 '18 at 16:52
  • Make sure to use the resteasy-jackon2-provider. The JavaTimeModule is for Jackson 2 – Paul Samsotha May 30 '18 at 16:53
  • Thank you very much Paul. Using the resteasy-jackon2-provider did the trick. – kopi_b Jun 01 '18 at 08:46
0

@XmlJavaTypeAdapter can apply to a whole package (see doc).

When @XmlJavaTypeAdapter annotation is defined at the package level it applies to all references from within the package to @XmlJavaTypeAdapter.type().

Try to add the file package-info.java next to the class that has the map. Given the package of the class is com.acme.beans and that of the adapter is com.acme.adapters, the content of the file should be something like:

@XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
package com.acme.beans;

import com.acme.adapters.LocalDateTimeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
Ahmad Shahwan
  • 1,662
  • 18
  • 29