I have a GET /object call with required parameter id:
#RequestMapping(value = {"/object"}, produces = "application/json", method = RequestMethod.GET)
public String getObject(#RequestParam(required = true) String id, HttpServletRequest request) {
// do stuff
...
// Send message
return json;
}
When it's called without the parameter id my spring application throws a :
Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'id' is not present] but the caller recieves a Whitelabel Error Page with a 400 status with no explanation on the missing parameter...
Is there a way to return to the caller what paramter is missing ?
You need to create a custom Error Page, which would describe the exceptions you declare. See here: https://www.baeldung.com/exception-handling-for-rest-with-spring
Related
I have an API endpoint that get a name and description parameters (both are mandatory)
createSomething(#RequestParam(value = "name") String name,#RequestParam(value = "description") String description)
If the client is not providing any of these he will get 400 Bad Request
Is there a way for me to tell the client which field is missing ? give more information for the "Bad Request" response
Update: Note that the parameters must be mandatory since I want that OpenAPI will detect that these parameters are mandatory. So solutions like making then "optional" and checking inside the body of the function is not what I am looking for
I see multiple answers but no one is specific enough.
1)
Spring by default has the capability of reporting in the error message what parameter was missing or other violations in the request.
However since spring boot version 2.3 the specific error messages are hidden, so that no sensitive information can be disclosed to the user.
You can use the property server.error.include-message: always which was the default mechanism before 2.3 version and allow spring to write error messages for you again.
2)
If you can't afford this because other sensitive info could be leaked from other exceptions, then you have to provide your own exception handler for this specific case
The following can be placed either in the same controller, or in another class marked with #ControllerAdvice
#ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity handleMissingParams(MissingServletRequestParameterException ex) {
return ResponseEntity.badRequest().body(String.format("Missing parameter with name:%s", ex.getParameterName()));
}
As #Shubam said, you can use the defaultValue attribute of #RequestParam annotation by setting the required attribute to true since both the parameters are mandatory.
Here is an example of how you could do it,
private final String DEFAULT_NAME = "Default Name";
private final String DEFAULT_DESC = "Default Desc";
#RequestMapping(value = "/get", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public ResponseEntity<String> createSomething(#RequestParam(required = true, name = "name", defaultValue = "Default Name") String name,
#RequestParam(required = true, name = "description", defaultValue = "Default Desc") String desc){
if(DEFAULT_NAME.equals(name)){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Field Name is missing");
}
if(DEFAULT_DESC.equals(desc)){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Field Desc is missing");
}
return ResponseEntity.ok(String.format("Hello, %s!",name));
}
You can use validation with a customised message :
#GetMapping("/name-for-month")
public String getNameOfMonthByNumber(#RequestParam #Min(1) #Max(value = 12, message = “month number has to be less than or equal to 12”) Integer month) {
// ...
}
There are many ways of handling errors for Rest find below a link of at least 5 solutions for your issue :
ExceptionHandler
HandlerExceptionResolver (ResponseStatusExceptionResolver this is the most adducate for your case or the 4th one if you use spring 5+)
ControllerAdvice
ResponseStatusException
Handle the Access Denied in Spring Security
https://www.baeldung.com/exception-handling-for-rest-with-spring
Since both parameters are mandatory you'll be getting 400 (bad request) if you try to send the request without paramters.
A workaround could be making request parameters non-mandatory(so that request can be sent without parameters) and provide a default value in case no parameter is provided
createSomething(#RequestParam(value = "name", required=false, defaultValue = null) String name,#RequestParam(value = "description", required=false, defaultValue = null) String description)
In the function, you can check for null like the following -
if (name == null) // name parameter is not provided
if (description == null) // description paramter is not provided
And, based on conditions you can also send error reponse if any one/more paramter not provided in the request.
I am new to Spring and I want to write a controller which will take Collection/Iterable as arguments. Like this:
#RequestMapping(value = "friends", method = RequestMethod.POST)
public #ResponseBody Callable<Iterable<User>>
getFriendsOfUser(#RequestParam(required = true, name = "mobiles") Iterable<String> mobs) {
// return callable
}
There is no compilation error, but I cannot make it work. Can you say how will this work? And how shall be the request to this api be constructed?
public String getFriendsOfUser(#RequestParam(required = true, value = "mobiles") String[] mobiless){
....
}
and your mobile should be
mobiles=myValue1&mobiles=myValue2&mobiles=myValue3
or
mobiles=myvalue1,myValue2,myValue3
still if you have any doubt post your front-end code and Ajax call.
You've mapped a POST method so you might need #RequestBody instead of #RequestParam
#RequestParam is, as the name implies, for request parameters: [host]/endpoint?param=foo&secondParam=bar
whereas
#RequestBody is for JSON/XML or any other type content sent as the request's body.
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?
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.
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.
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.
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());
}
I'm developing a web application with Spring MVC and Thymeleaf as my ViewResolver. I have the following controller handler method:
#RequestMapping(value = "/something", method = RequestMethod.POST, params = "submit")
public String doSomething(#ModelAttribute("error") String error /*, other attributes */) {
// find out if there is an error
error = getErrorMessage();
return "someHTMLfile";
}
My view contains this line:
<p><span th:text="${error}">Error Message goes here</span></p>
When executed, the tag does not render to anything. This is probably due to ${error} evaluating to an empty string but I can't understand why. Doesn't Spring's #ModelAttribute annotation add the object to the model map automatically, where Thymeleaf can find it?
If I instead have:
#RequestMapping(value = "/something", method = RequestMethod.POST, params = "submit")
public String doSomething(ModelMap map /*, other attributes */) {
// find out if there is an error
String error;
error = getErrorMessage();
map.addAttribute("error", error);
return "someHTMLfile";
}
The view is rendered perfectly fine with the error message. Does #ModelAttribute not add the object to the request model?
Edit: I've tried doing both:
#RequestMapping(value = "/something", method = RequestMethod.POST, params = "submit")
public String doSomething(#ModelAttribute("error") String error, ModelMap map /*, other attributes */) {
// find out if there is an error
error = getErrorMessage();
map.addAttribute("error", error);
return "someHTMLfile";
}
This also doesn't work.
Actually I don't think your issue is related to Thymeleaf, just SpringMVC :-)
In your first snippet, you don't add anything to the request model but try to get an object called "error" back from the form.
In your second snippet, you do add an object to the model, that's why your view is well rendered.
Take a look at the SpringMVC doc here (16.3.3.8) to have a better understanding of the #ModelAttribute annotation on a method argument.
I feel stupid but whatever, we all make mistakes.
Spring was creating a new String instance for me and injecting it into my method and into the model under the key error. String is an immutable object, so when I do error = getErrorMessage(), I assign another instance to my error object. Now there is my error and the error String in Spring's model with a value of "". That's why Thymeleaf rendering only finds the empty string.
I want to trigger 404 page whenever I wasn't passed all of the parameters. Lets say I have the following URI:
/myapp/op?param1=1¶m2=2#param3=3
In case on of the parameters wasn;t invoked I want to return 404 page. I tried doing:
#ResponseStatus(HttpStatus.NOT_FOUND)
#RequestMapping(value = "op", params = { "!param1" })
public void missingArg() {
}
but then I get an exception telling me there is ambiguity between methods that handle missing second and third parameter.
How can I accomplish this, then?
If you're using Spring 3.1 you can define an exception class like so:
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public final class ResourceNotFoundException extends RuntimeException {
// class definition
}
Now whenever you throw that exception, Spring will return the http status defined in your #ResponseStatus annotation. For example:
#RequestMapping(value = "/op")
public void methodWithRequestParams(#RequestParam(value = "param1", required = false) String param1,
#RequestParam(value = "param2", required = false) String param2) {
if (param1 == null || param2 == null) {
throw new ResourceNotFoundException();
}
}
will return a 404 whenever param1 or param2 is null.
You do not have to implement the missingArg() function. If there is no matching method for the incoming request, then Spring's HandlerExceptionResolver will handle it and return a response with an appropriate status code.
Spring will automatically convert the request parameters into method parameters if you use the #RequestParam annotation:
#RequestMapping(value = "/op")
public void methodWithRequestParams(#RequestParam("param1") String param1,
#RequestParam("param2") String param2,
#RequestParam("param3") String param3) {
// do something with params
}
By convention, the methodWithRequestParams() method will not be called if not all params are part of the request (unless the required attribute of the #RequestParam is set to false).
Also note that the parameters does not have to be Strings.
Echoing what matsev said in the comments of another answer, you should not be using #ResponseStatus(HttpStatus.NOT_FOUND) in this case, but rather #ResponseStatus(HttpStatus.BAD_REQUEST).
#ResponseStatus(HttpStatus.NOT_FOUND) should be used when the request was formed properly, but the resource isn't there.