Skip to content

Instantly share code, notes, and snippets.

@dschulten
Last active September 26, 2022 11:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dschulten/4718b60b95bf9253dab8009febfa6d24 to your computer and use it in GitHub Desktop.
Save dschulten/4718b60b95bf9253dab8009febfa6d24 to your computer and use it in GitHub Desktop.
Set default timezone of jax-rs jackson provider for deserialization of timestamp to XmlGregorianCalendar JAXB date field.
package de.kommone.kmc.api;
import java.util.TimeZone;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.ObjectMapper;
@Provider
@Produces("application/json")
public class JacksonConfigurator implements ContextResolver<ObjectMapper> {
private ObjectMapper mapper = new ObjectMapper();
public JacksonConfigurator() {
mapper.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
}
@Override
public ObjectMapper getContext(Class<?> arg0) {
return mapper;
}
}
...
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.25.1</version>
<exclusions>
<exclusion>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-jaxb</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.25.1</version>
</dependency>
...
<servlet>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
@dschulten
Copy link
Author

dschulten commented Jan 30, 2021

The default jax-rs jackson provider uses UTC as timezone, so that a date which has been parsed from a String representation in a different timezone and serialized as JSON timestamp gets deserialized with a wrong offset by the jax-rs service. The root problem is of course that the source system uses its local timezone to parse a date string into a timestamp. But if you cannot fix that, here is how you can compensate.

Scenario:

  • Different application parses a date string with the system default timezone (e.g. Europe/Berlin) into a timestamp. This results in a timestamp which is at midnight of the parsed date in Europe/Berlin, i.e. in UTC the timestamp is one hour (or two hours, depending on summer time) before midnight of the parsed date.
  • application serializes timestamp as JSON
  • application sends JSON to jax-rs service with org.glassfish.jersey.mediajersey-media-json-jackson serializer
  • jackson by default uses UTC to create an XMLGregorianCalendar, regardless of the system timezone
  • The XMLGregorianCalendar now contains a date which is one day behind the original date string

This code allows to specify the timezone which should be used by Jackson when creating a timezoned date type like XMLGregorianCalendar from a JSON timestamp value. The @Provider annotation activates the custom resolver for ObjectMapper, which injects the customized ObjectMapper, if class scanning is active.

The ObjectMapper can be fully customized with custom deserializers, date format and other configuration items like Inclusion.NON_NULL, see https://stackoverflow.com/a/5234682/743507 for an example.

Note: If a custom jax-rs Application annotated with @ApplicationPath is present to support annotation-only setup without web.xml, but it overrides getClasses or getSingleton , then class scanning is deactivated as hinted by section 2.3.2 of the jax-rs spec.
I.e. in that case you have to return the JacksonConfigurator as one of the classes returned by getClasses. Otherwise your resource classes will not be picked up and you get 404 when sending requests.

Related code: com.fasterxml.jackson.databind.ext.CoreXMLDeserializers.Std#_gregorianFromDate

@JeeDevUser
Copy link

Hi,
Just doing that, the JacksonConfigurator constructor is called, but getContext() method is never called.
What could be a problem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment