41

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
  • 863
  • 3
  • 11
  • 23

4 Answers4

61

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
  • 43,096
  • 14
  • 154
  • 142
  • @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
20

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
  • 726
  • 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
5

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,227
  • 12
  • 57
  • 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,044
  • 1
  • 45
  • 58