Skip to content

UriBuilder query parameter encoding behaves differently depending on required RequestParameter #1565

@pax95

Description

@pax95

I'm upgrading from sb 1.5.9.RELEASE to 2.5.1 i steps and noticed a different behaviour in how the UriBuilder handles encoding of query parameters depending on if you set the RequestParam required to true or false.
The queryparamters ZonedDateTime has to be encoded (+sign), but is not if the RequestParam(required=false).
I would expect the parameter to be encoded no matter if it's required or not.
Running the example code will result in the first 'from' parameter to be encoded, but the 'to' not.
I think the issue lies in the DefaultUriBuilder or related. Spring Hateoas uses this to render links.

Following the link will result in ->
Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: 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.RequestParam @org.springframework.format.annotation.DateTimeFormat java.time.ZonedDateTime] for value '2021-06-18T14:59:05.802044 02:00[Europe/Copenhagen]'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2021-06-18T14:59:05.802044 02:00[Europe/Copenhagen]]]
Because + is treated as space.

Sample application ->

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

import java.time.ZonedDateTime;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @RestController
    public class DemoController {
        @GetMapping(value = "/search/findByDate", produces = MediaType.APPLICATION_JSON_VALUE)
        public ResponseEntity<Void> findByDate(@RequestParam(name = "from", required = true) @DateTimeFormat(iso = ISO.DATE_TIME) ZonedDateTime from,
                                               @RequestParam(name = "to", required = false) @DateTimeFormat(iso = ISO.DATE_TIME) ZonedDateTime to) {
            return ResponseEntity.ok().build();
        }

        @GetMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
        public ResponseEntity<RootResource> getRoot() {
            return ResponseEntity.ok(new RootResource());
        }
    }

    public class RootResource extends RepresentationModel<RootResource> {
        public RootResource() {
            add(linkTo(methodOn(DemoController.class).findByDate(null, null)).withRel("hsf").expand(ZonedDateTime.now(), ZonedDateTime.now().plusMonths(1)));
        }

    }

}

Setting both parameters to required = true renders the link correctly.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions