47

Consider the following mapping:

@RequestMapping(value = "/superDuperPage", method = RequestMethod.GET)
public String superDuperPage(@RequestParam(value = "someParameter", required = true) String parameter)
{
    return "somePage";
}

I want to handle the missing parameter case by not adding in required = false. By default, 400 error is returned, but I want to return, let's say, a different page. How can I achieve this?

peech
  • 931
  • 3
  • 12
  • 23

5 Answers5

72

If a required @RequestParam is not present in the request, Spring will throw a MissingServletRequestParameterException exception. You can define an @ExceptionHandler in the same controller or in a @ControllerAdvice to handle that exception:

@ExceptionHandler(MissingServletRequestParameterException.class)
public void handleMissingParams(MissingServletRequestParameterException ex) {
    String name = ex.getParameterName();
    System.out.println(name + " parameter is missing");
    // Actual exception handling
}

I want to return let's say a different page. How to I achieve this?

As the Spring documentation states:

Much like standard controller methods annotated with a @RequestMapping annotation, the method arguments and return values of @ExceptionHandler methods can be flexible. For example, the HttpServletRequest can be accessed in Servlet environments and the PortletRequest in Portlet environments. The return type can be a String, which is interpreted as a view name, a ModelAndView object, a ResponseEntity, or you can also add the @ResponseBody to have the method return value converted with message converters and written to the response stream.

Ali Dehghani
  • 46,221
  • 15
  • 164
  • 151
  • @Ali this worked perfectly. thank you for your help. Also, is there a way of knowing which method threw this exception? I can't find it in `printStack()`. – peech Jun 10 '16 at 11:36
  • @peech The exception only encapsulates param type and param name information. So No, you can't know what method throws the exception. – Ali Dehghani Jun 10 '16 at 11:48
  • @AliDehghani How to find the parameter names in case of multiple parameters missing? – V.Vidyasagar Apr 06 '21 at 14:38
21

An alternative

If you use the @ControllerAdvice on your class and if it extends the Spring base class ResponseEntityExceptionHandler. A pre-defined function has been created on the base class for this purpose. You have to override it in your handler.

    @Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    String name = ex.getParameterName();
    logger.error(name + " parameter is missing");

    return super.handleMissingServletRequestParameter(ex, headers, status, request);
}

This base class is very useful, especially if you want to process the validation errors that the framework creates.

Eric Giguere
  • 766
  • 5
  • 9
  • 2
    More verbose explanations and examples about @ContollerAdvice can be found in https://www.toptal.com/java/spring-boot-rest-api-error-handling – Augustus Kling Feb 28 '19 at 20:45
  • This worked for me. The accepted answer did not work as I was getting "Ambiguous @ExceptionHandler method mapped for..." – MIK Apr 26 '22 at 22:11
  • this approach is no longer valid for sprinboot 3 /spring 6. The method handleMissingServletRequestParameter is not overridable anymore. – tonnoz Feb 22 '23 at 09:25
7

You can do this with Spring 4.1 onwards and Java 8 by leveraging the Optional type. In your example that would mean your @RequestParam String will have now type of Optional<String>.

Take a look at this article for an example showcasing this feature.

dimitrisli
  • 20,895
  • 12
  • 59
  • 63
0

Maybe not that relevant, but I came across to a similar need: change the 5xx error to 4xx error for authentication header missing.

The controller is as follows:

@RequestMapping("list")
public ResponseEntity<Object> queryXXX(@RequestHeader(value = "Authorization") String token) {
...
}

When you cURL it without the authorization header you get a 5xx error:

curl --head -X GET "http://localhost:8081/list?xxx=yyy" -H "accept: */*"

HTTP/1.1 500
...

To change it to 401 you can

@ExceptionHandler(org.springframework.web.bind.MissingRequestHeaderException.class)
@ResponseBody
public ResponseEntity<Object> authMissing(org.springframework.web.bind.MissingRequestHeaderException ex) {
        log.error(ex.getMessage(), ex);

        return IResponse.builder().code(401).message(ex.getMessage()).data(null).build();
}


@Data
public class IResponse<T> implements Serializable {
    private Integer code;
    private String message = "";
    private T data;
...
}

You can verify it by an automation test:

@Test
void testQueryEventListWithoutAuthentication() throws Exception {
    val request = get("/list?enrollEndTime=1619176774&enrollStartTime=1619176774&eventEndTime=1619176774&eventStartTime=1619176774");
    mockMvc.perform(request).andExpect(status().is4xxClientError());
}
Jeff Tian
  • 5,210
  • 3
  • 51
  • 71
0

I had this same issue today (30/08/2023) using Spring 3.1.3. You can solve this issue by overriding the protected function handleMissingServletRequestParameter of the ResponseEntityExceptionHandler base class.

@ControllerAdvice
class DevBlogExceptionHandler : ResponseEntityExceptionHandler() {

    override fun handleMissingServletRequestParameter(
        ex: MissingServletRequestParameterException,
        headers: HttpHeaders,
        status: HttpStatusCode,
        request: WebRequest
    ): ResponseEntity<Any>? {

        val errorMessages = listOf("Required query parameter \"${ex.parameterName}\" is missing")

        val error = DevBlogException(
            status = HttpStatus.BAD_REQUEST,
            errors = errorMessages,
            timeStamp = LocalDateTime.now(),
            statusCode = status.value()
        )

        return ResponseEntity<Any>(error, error.status);
    }

}