Search code examples
jax-rsresteasylocaldate

Formatting LocalDateTime for JAX-RS


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


Solution

  • 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.