2

I am using org.springframework.boot:spring-boot-starter-validation:2.7.0(which in turn uses hibernate validator) to validate user input to rest controller.
I am using Spring Boot Web Starter (2.7.0) based project with @RestController annotation My @GetMapping method is something like below -

@GetMapping(path = "/abcservice")
public Object abcService(
            @RequestParam(value = "accountId", required = true) String accountId,
            @Valid @RequestParam(value = "offset", required = false, defaultValue = "0") int offset,
            @Valid @RequestParam(value = "limit", required = false, defaultValue = "10000") int limit
    ) throws Exception {

My problem is - I want the user to know about any input validation errors so they can correct and retry. But the framework is just giving 400 status code with below message.

{
    "timestamp": "2022-08-03T16:10:14.554+00:00",
    "status": 400,
    "error": "Bad Request",
    "path": "/somepath/abcservice"
}

On the server side the request is logged in warn.

2022-08-03 21:40:14.535 WARN 98866 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: "0s"]

I want this above error message --> Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: "0s" also to be passed on to user. Is there a easy configuration based way to achieve.
I think i can add a ControllerAdvice to handle this exception and include this message in the response from handler method. But this will be a couple of lines of code. Is there an even simpler way than the ControllerAdvice approach.

Similarly if the client don't pass the mandatory accountId param, the client is just getting the same 400 response as above. No details or hints to the client about what wrong they are doing or how they can fix it.. but on the server side i can see below warn log.

2022-08-03 21:59:20.195 WARN 235 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'accountId' for method parameter type String is not present]

I want the client to know about this error/exception. Nothing secret here to hide (atleast in my case).

Edit - found this config -

server.error.include-message=always

Now the issue is, bad requests are sent with 500 status code, I want them to sent as 400. Then this Q is solved.
Validations made by @Valid return with 500 Status Code. Is there anyway to tell the server to return 400 response when validations fail (without using ControllerAdvice).

If you wish to test-- you can try -->
Annotate controller with @Validated.
And execute below method and you will see 500 error but would want this to be 400.

@GetMapping("/test")
public void test(@Valid @RequestParam(value = "studentId", required = false)
                         @Min(value=0, message="Can not be less than 0") @Max(value=200, message="Can not be above 200") Long studentId ) {

        System.out.println("hit: ");
    }

And hit - http://localhost:9099/test?studentId=400

samshers
  • 1
  • 6
  • 37
  • 84
  • Well, can't you just catch `MissingServletRequestParameterException` and return `exception.getMessage()` as string to client, if you don't want to customize response using ControllerAdvice. – Enfield Li Aug 03 '22 at 16:38
  • @Enfieldli - i don't think one can catch this exception anywhere else then in `ControllerAdvice`. I am looking if there is simpler approach through framework.. like `show_validation_error_messages_to_client=true|false` – samshers Aug 03 '22 at 16:41
  • `server.error.include-message` - see edit @Enfieldli. Just need to convert 500 to 400 now with out a controller advice – samshers Aug 04 '22 at 04:55
  • this should answer - regarding 500 vs 400 [@PathVariable validation gives 500 instead of 400 #10471](https://github.com/spring-projects/spring-boot/issues/10471) – samshers Aug 04 '22 at 05:11

2 Answers2

2

The spring in-built solution without global exception handler and with minimal config is by adding the below property in the application.properties.

server.error.include-binding-errors=always

The above property can have three values:

  1. always ----> All api's in the app will always return well defined validation error message response.
  2. on-param ----> All api's in the app will conditionally return well defined validation error message response based on input request param field "errors"
  3. never ---> to disable this feature.

Example Github Project Reference

Demo Test:

package com.example.demo;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApplication {

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

    @PostMapping("/test")
    public void test(@Valid @RequestBody Student student) {
        System.out.println("studentData: " + student);
    }

}

class Student {

    @NotNull(message = "firstName cannot be null")
    private String firstName;
    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return "Student [firstName=" + firstName + ", lastName=" + lastName + "]";
    }

}

Request:

{
    "firstName": null,
    "lastName" : "sai"
}

Response: (with HTTP response code = 400)

{
    "timestamp": "2022-08-04T05:23:58.837+00:00",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "NotNull.student.firstName",
                "NotNull.firstName",
                "NotNull.java.lang.String",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "student.firstName",
                        "firstName"
                    ],
                    "arguments": null,
                    "defaultMessage": "firstName",
                    "code": "firstName"
                }
            ],
            "defaultMessage": "firstName cannot be null",
            "objectName": "student",
            "field": "firstName",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotNull"
        }
    ],
    "path": "/test"
}
Arun Sai Mustyala
  • 1,736
  • 1
  • 11
  • 27
  • +1. but still validations made by `@Valid` return with `500` Status Code. Is there anyway to tell the server to return `400` response when validations fail – samshers Aug 04 '22 at 09:43
  • Please clone my project from github reference and test it by passing first name as null. You will receive 400 status code only and not 500. – Arun Sai Mustyala Aug 04 '22 at 12:07
  • i tried this. Added this stuff to you project - `@Validated public class DemoApplication { ...` AND `@GetMapping("/test") public void test(@Valid @RequestParam(value = "studentId", required = false) @Min(value=0, message="Can not be less than 0") @Max(value=200, message="Can not be above 200") Long studentId ) { System.out.println("hit: "); }` Now try url --> http://localhost:9099/test?studentId=400 And you will get 500 error – samshers Aug 04 '22 at 13:18
  • appreciate your involvment and help.. plz see if in the above case if there's anything one can do to change the 500 code to 400. – samshers Aug 04 '22 at 13:20
  • 1
    true, it's showing 500. the only next solution would be to implement an exception handler for constraintviolation exception and return a response accordingly. – Arun Sai Mustyala Aug 05 '22 at 05:44
  • 1
    np..Accepted as there seems to be NO easy work around. But that's what i wanted to avoid i.e controller advice. It seems to make no sense why framework is returning 500 when it's client error. they should fix it.. i hope you have seen this https://github.com/spring-projects/spring-boot/issues/10471 they should fix it. – samshers Aug 05 '22 at 06:26
1

Use @expection handler and controller advice this help to handle your issue

  • abhi - thanks for your suggestion, but as stated in the Q, I am aware about this solution and am looking to see if there is an even simpler way to pass on the error to client. +1 – samshers Aug 03 '22 at 17:41