DavidR
DavidR

Reputation: 6962

ZonedDateTime as PathVariable in Spring REST RequestMapping

I have a REST endpoint in my Spring application that looks like this

@RequestMapping(value="/customer/device/startDate/{startDate}/endDate/{endDate}", method=RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)
public Page<DeviceInfo> getDeviceListForCustomerBetweenDates(@PathVariable ZonedDateTime startDate, @PathVariable ZonedDateTime endDate, Pageable pageable) {
    ... code here ...
}

I have tried passing in the path variables as both milliseconds and seconds. However, I get the following exception both ways:

"Failed to convert value of type 'java.lang.String' to required type 'java.time.ZonedDateTime'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type java.lang.String to type @org.springframework.web.bind.annotation.PathVariable java.time.ZonedDateTime for value '1446361200'; nested exception is java.time.format.DateTimeParseException: Text '1446361200' could not be parsed at index 10"

Can someone please explain how I can pass in (either as seconds or milliseconds) a String such as 1446361200 and get it to convert to ZonedDateTime?

Or is the only way to pass as String and then do the conversion myself? If so is there a generic way to get this handled for multiple methods with similar design?

Upvotes: 15

Views: 14786

Answers (3)

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 280031

There's a default converter for ZonedDateTime parameters. It uses Java 8's DateTimeFormatter created like

DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);

This could've been any FormatStyle or really any DateTimeFormatter as far as you're concerned, your example wouldn't have worked. DateTimeFormatter parses and formats to date strings, not timestamps, which is what you've provided.

You could have provided an appropriate custom @org.springframework.format.annotation.DateTimeFormat to your parameter such as

public Page<DeviceInfo> getDeviceListForCustomerBetweenDates(
    @PathVariable @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) ZonedDateTime startDate, 
    @PathVariable @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) ZonedDateTime endDate, 
    Pageable pageable) { ...

or with an appropriate pattern and a corresponding date string like

2000-10-31T01:30:00.000-05:00

You won't be able to do any of the above with a unix timestamp. The canonical way to do timestamp to ZonedDateTime conversion is through Instant#ofEpochSecond(long), given an appropriate ZoneId.

long startTime = 1446361200L;
ZonedDateTime start = Instant.ofEpochSecond(startTime).atZone(ZoneId.systemDefault());
System.out.println(start);

To make this work with @PathVariable, register a custom Converter. Something like

class ZonedDateTimeConverter implements Converter<String, ZonedDateTime> {
    private final ZoneId zoneId;

    public ZonedDateTimeConverter(ZoneId zoneId) {
        this.zoneId = zoneId;
    }

    @Override
    public ZonedDateTime convert(String source) {
        long startTime = Long.parseLong(source);
        return Instant.ofEpochSecond(startTime).atZone(zoneId);
    }
}

And register it in a WebMvcConfigurationSupport @Configuration annotated class, overriding addFormatters

@Override
protected void addFormatters(FormatterRegistry registry) {
    registry.addConverter(new ZonedDateTimeConverter(ZoneId.systemDefault()));
}

Now, Spring MVC will use this converter to deserialize the String path segment into a ZonedDateTime object.


In Spring Boot, I think you can just declare a @Bean for the corresponding Converter and it will register it automatically, but don't take my word for it.

Upvotes: 18

Powerlord
Powerlord

Reputation: 88796

By default, @PathVariable only supports a limited set of types including the 7 basic types (boolean, byte, short, int, long, float, and double) plus Date.

However, it is possible to extend that using an @InitBinder in your controller and adding a binding for ZonedDateTime.class. I'm not sure if you can define this in an external class, though... if not, you need to define this @InitBinder in every controller that uses a ZonedDateTime.

Edit: You may need to create a custom PropertyEditor and bind it using registerCustomEditor to do this.

Upvotes: 1

OPK
OPK

Reputation: 4180

You gonna need to accept what you passed to @PathVariable as a String data type, then do the conversion yourself, your error logs pretty clearly tells you that.

Spring library cannot conver String values"1446361200" to ZonedDateTime type through @PathVariable binding unfortunately.

Upvotes: 5

Related Questions