Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 800
DavidR Avatar asked Dec 21 '15 14:12

DavidR


People also ask

Is PathVariable required by default?

From Spring 4.3. 3 version, @PathVariable annotation has required attribute, to specify it is mandatorily required in URI. The default value for this attribute is true if we make this attribute value to false, then Spring MVC will not throw an exception.

What is the difference between PathParam and PathVariable?

The @PathVariable annotation is used for data passed in the URI (e.g. RESTful web services) while @RequestParam is used to extract the data found in query parameters. These annotations can be mixed together inside the same controller. @PathParam is a JAX-RS annotation that is equivalent to @PathVariable in Spring.

How do you pass a date as a Spring boot parameter?

One of the ways to handle this problem is to annotate the parameters with the @DateTimeFormat annotation, and provide a formatting pattern parameter: @RestController public class DateTimeController { @PostMapping("/date") public void date(@RequestParam("date") @DateTimeFormat(iso = DateTimeFormat.

How do you use PathVariable?

The @PathVariable annotation is used to extract the value from the URI. It is most suitable for the RESTful web service where the URL contains some value. Spring MVC allows us to use multiple @PathVariable annotations in the same method. A path variable is a critical part of creating rest resources.


2 Answers

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.

like image 149
Sotirios Delimanolis Avatar answered Oct 16 '22 22:10

Sotirios Delimanolis


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.

like image 43
OPK Avatar answered Oct 16 '22 23:10

OPK