Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exceptions for missing request values should expose information when they are missing after conversion #26679

Closed
drahkrub opened this issue Mar 15, 2021 · 6 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@drahkrub
Copy link

drahkrub commented Mar 15, 2021

In Spring 5.3 it is possible that a MissingServletRequestParameterException is thrown although a required parameter is provided (and therefore not missing). The same holds for a required path variable - even it is provided a MissingPathVariableException may be thrown.

Moreover the MissingServletRequestParameterException triggers a response status 400 in contrast to the MissingPathVariableException which ends up in a response status 500.

The former happens due to the changes made in #23939: If a provided parameter (request parameter or path variable) is converted to null it is handled as a missing value, see AbstractNamedValueMethodArgumentResolver.

I would like to propose:

  1. Adjust the response status.
  2. Throw a different exception (e.g. IllegalArgumentException) if a required parameter (request parameter or path variable) is provided but converted to null.

In a lengthy discussion with @rstoyanchev in #26088 (I thank his patience) I gave an example to explain that the current status is problematic:

  1. two users load the same book entity via its id, let's say 42
  2. one user deletes the book entity
  3. the other user continues using URLs with the id 42

I attached a small spring boot app with several test cases (github, spring_sandbox-master.zip).

With

@GetMapping(path = "/rp/book")
public Book getBookByRequestParam(@RequestParam("id") Book book) {
    ...

calling /rp/book?id=42 leads to a MissingServletRequestParameterException which is also thrown if /edit is called without a parameter or with an empty parameter - these cases can not be distinguished in an elegant way, see the tests

  • noRequestParamThrowsMissingServletRequestParameterException
  • emptyRequestParamThrowsMissingServletRequestParameterException
  • idOfDeletedBookThrowsMissingServletRequestParameterException

In contrast calling

@GetMapping({"/pv/book/{id}"})
public Book getBookByPathVariable(@PathVariable("id") Book book) {
    ...

with /pv/book/42 "only" leads to a misleading MissingPathVariableException, but the method can not be called with no or an empty parameter (if /pv/book is not mapped to a different method it is handled by the ResourceHttpRequestHandler, see the test missingValueNotHandledByController).

But a conflicting case can be constructed this way:

@GetMapping({"/cc/book", "/cc/book/{id}"})
public Book constructedCase(@PathVariable("id") Book book) {
    ...

Here calling /cc/book and /cc/book/42 both lead to a MissingPathVariableException - but as I said this is constructed resp. bad programming.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Mar 15, 2021
@drahkrub
Copy link
Author

And a general remark: For me the changes made in Spring 5.3 feel like a strange (not to say wrong) mix of concepts.

@RequestParam(required = true) always ment to me that the presence of the request param is required, not that its value is guaranteed to not result in null.

To express "guaranteed to not result in null" I would see or create something like @RequestParam(nullable = false).

In my discussion with @rstoyanchev in #26088 there are some inaccuracies on my side but interested readers can find more arguments over there (from an old school programmer who is using Spring for around 15 years now and who is picking up innovations (not only in the java programming language) only slowly ;-)).

@rstoyanchev rstoyanchev added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Mar 22, 2021
@rstoyanchev
Copy link
Contributor

rstoyanchev commented Mar 22, 2021

@drahkrub indeed at the point of raising the MissingServletRequestParameterException we do know if the parameter is present and non-empty. We can update the exception with a property such as conversionFailed and an error message indicative of the the fact the value was present but was converted to null. This would make it easy to differentiate and handle the error differently with an @ExceptionHandler either within a controller or across controllers via @ControllerAdvice.

Beyond that there are more options. For example in your own Converter (as in your sample), you can return null, raise a different exception (e.g. entity not found), or create a default value. Or to handle it locally within a controller method, declare the argument as @Nullable or Optional. In any case when an entity is null, I imagine either a 404 or a 400 is in order so an @ExceptionHandler method is probably the way to go.

We've debated how @RequestParam or @PathVariable handle opting out of being required, and I would like to add that it is consistent with how the same is done across the Spring Framework for annotation-based handler methods and for dependency injection, including in constructor args. There is an established pattern for that already and we're not going to add a nullable attribute to annotations.

@rstoyanchev rstoyanchev added status: waiting-for-feedback We need additional information before we can continue type: enhancement A general enhancement labels Mar 22, 2021
@rstoyanchev rstoyanchev self-assigned this Mar 22, 2021
@drahkrub
Copy link
Author

@rstoyanchev Related to the three paragraphs you wrote:

  1. That would be a step forward! Although a special exception (maybe extending MissingServletRequestParameterException) would be more practical I think.
  2. "raise a different exception (e.g. entity not found)" - as you wrote, that only works in my own Converters, not in e.g. Spring Datas DomainClassConverter. All other options (including @Nullable and Optional) also have disadvantages...
  3. Ok, I didn't seriously expect that either, but I just couldn't resist to raise this point one more time (and for the last time) again ;-)

What about the different response status? Are they intentional?

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 22, 2021
@rstoyanchev rstoyanchev added this to the 5.3.6 milestone Mar 23, 2021
@rstoyanchev rstoyanchev removed status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged or decided on labels Mar 23, 2021
@rstoyanchev rstoyanchev changed the title @RequestParam and @PathVariable produce misleading MissingServletRequestParameterException resp. MissingPathVariableException (and produce different response status) Exceptions for missing request values should expose information when they are missing after conversion Mar 29, 2021
@rstoyanchev
Copy link
Contributor

I've added a missingAfterConversion flag with a common base class for all exceptions related to missing request values. Due to the number of exceptions it's not feasible to go with a different exception (as a sub-class) approach.

@Stexxen
Copy link

Stexxen commented Apr 26, 2021

Just wanted to make a note that this change appeared during an upgrade from Spring Boot 2.4.4 to 2.4.5 and it broke some of our tests where we are checking the exceptions getMessage() after it has been converted to a json response.

Should a point release dramatically change an exceptions message is what I'm asking here? I could understand this appearing in an upgrade of 5.3.x to 5.4.0 but a point release.... I'm not so sure...

Zoran0104 pushed a commit to Zoran0104/spring-framework that referenced this issue Aug 20, 2021
Zoran0104 pushed a commit to Zoran0104/spring-framework that referenced this issue Aug 20, 2021
lxbzmy pushed a commit to lxbzmy/spring-framework that referenced this issue Mar 26, 2022
lxbzmy pushed a commit to lxbzmy/spring-framework that referenced this issue Mar 26, 2022
@skaba
Copy link

skaba commented Nov 2, 2022

Will this be backported to Spring 5?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

5 participants