7

I'm trying to pass a date to a JAX-RS service. Checking other questions like: Date format Mapping to JSON Jackson

The answers and the documentation show that there is a jackson annotation which should allow date formatting.

public class SolutionFilter {
    @MatrixParam("toDate")
    @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd", timezone="CET")
    private Date toDate;

    public void setToDate(Date toDate) {
        this.toDate = toDate;
    }
}

Upon calling the Rest-Service I'm getting a ParseException:

Caused by: java.text.ParseException: Unparseable date: "2016-01-01"
  at java.text.DateFormat.parse(DateFormat.java:366)
  at org.glassfish.jersey.message.internal.HttpDateFormat.readDate(HttpDateFormat.java:137)
  at org.glassfish.jersey.server.internal.inject.ParamConverters$DateProvider$1.fromString(ParamConverters.java:259)

It seems like the annotation is ignored. Debugging the parse method the pattern is set to EEE, dd MMM yyyy HH:mm:ss zzz and EEE MMM d HH:mm:ss yyyy.

I'm using Spring 4.2.1, Jersey 2.22 which binds jackson 2.5.4.

How can I get the dates parsed with the correct pattern?

Update: Thinking further about it the JSON is only used for output parsing. But this is probably about JAX-RS parameter parsing.

Community
  • 1
  • 1
Udo Held
  • 12,314
  • 11
  • 67
  • 93
  • why cant you just accept the `date` as `string` and do parse it later in your code.. – Sajan Chandran Sep 30 '15 at 10:25
  • @SajanChandran that would indeed work and I'm aware of it. However I don't consider it being clean code. If nothing helps, I will fall back to it. – Udo Held Sep 30 '15 at 10:28

1 Answers1

6

Param conversion is done with ParamConverters. If you follow the stacktrace, you will see the ParamConverters$DateProvider. The next call is the HttpDateFormat class which does the parsing.

If you look at the top of the class, you will see the date formats supported. These are standard HTTP data formats

/**
 * The date format pattern for RFC 1123.
 */
private static final String RFC1123_DATE_FORMAT_PATTERN = "EEE, dd MMM yyyy HH:mm:ss zzz";
/**
 * The date format pattern for RFC 1036.
 */
private static final String RFC1036_DATE_FORMAT_PATTERN = "EEEE, dd-MMM-yy HH:mm:ss zzz";
/**
 * The date format pattern for ANSI C asctime().
 */
private static final String ANSI_C_ASCTIME_DATE_FORMAT_PATTERN = "EEE MMM d HH:mm:ss yyyy";

As far as I know or can tell, there is no configuration available where we can add to this list. The only other option is to write your own converter. For example

@Provider
public class DateParamConverterProvider implements ParamConverterProvider {

    private final String format;

    public DateParamConverterProvider(String dateFormat) {
        this.format = dateFormat;
    }

    @Override
    public <T> ParamConverter<T> getConverter(Class<T> rawType, 
                                              Type genericType, 
                                              Annotation[] annotations) {

        if (rawType != Date.class) { return null; }

        return (ParamConverter<T>) new ParamConverter<Date>() {

            @Override
            public Date fromString(String value) {
                SimpleDateFormat formatter = new SimpleDateFormat(format);
                try {
                    return formatter.parse(value);
                } catch (Exception ex) {
                    throw new WebApplicationException("Bad formatted date", 400);
                }
            }

            @Override
            public String toString(Date date) { 
                return new SimpleDateFormat(format).format(date); 
            }
        };
    }
}

Here is a complete test case using Jersey Test Framework

public class DateParamTest extends JerseyTest {

    private static final String FORMAT = "MM-dd-yyyy";

    @Path("date")
    public static class DateResource {
        @GET
        public String get(@MatrixParam("since") Date date) {
            return new SimpleDateFormat(FORMAT).format(date);
        }
    }

    @Override
    public ResourceConfig configure() {
        return new ResourceConfig(DateResource.class)
                .register(new DateParamConverterProvider(FORMAT));
    }

    @Test
    public void should_return_same_date_and_format() {
        final String date = "09-30-2015";
        Response response = target("date").matrixParam("since", date)
                .request().get();
        assertEquals(200, response.getStatus());
        String returnDate = response.readEntity(String.class);
        assertEquals(date, returnDate);
        System.out.println(returnDate);
    }
}

Here is the dependency for the test framework

<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>${jersey2.version}</version>
    <scope>test</scope>
</dependency>

See Also:

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Works well. Thanks! I was already looking at the ParamConverter, however I missed to register it. I added a not null check for the date parsing as well. – Udo Held Sep 30 '15 at 11:23