I have two methods in my controller class with two unique URLs.
The first one has one optional parameter called name, which is a required parameter in the second URL. This seems to confuse Spring Boot when used.
First method:
#GetMapping(value = "/", params= {"id", "applicantid", "startingdate", "endingdate"})
public List<Event> getEventsById(#RequestParam("id") String sourceid,
#RequestParam("applicantid") String applicantid,
#RequestParam("startingdate") String startingdate,
#RequestParam("endingdate") String endingdate,
#RequestParam(value = "name", required = false) String name) {
The second one:
#GetMapping(value = "/", params= {"applicantid", "name", "startingdate", "endingdate"})
public List<Event> getEventsByApplicantId(#RequestParam("applicantid") String applicantid,
#RequestParam("name") String name,
#RequestParam("startingdate") String startingdate,
#RequestParam("endingdate") String endingdate) {
Now they both work fine, unless I add the optional parameter to the first URL like so:
/?id=999&applicantid=1&startingdate=2020&endingdate=2021&name=mobileapp
Spring Boot somehow thinks that I am trying to use the second method and it ignores the parameter "id" when parameter "name" is added, and gives me Ambiguous handler error. This is ofc because when "id" is ignored, the URL fits with the second one.
Shouldn't the "id" param be sufficient enough for Spring Boot to understand what method should be used?
I also noticed, that I could add all bunch of random parameters, and Spring Boot would always choose the second URL.
Do I need to validate the parameters, so that unknown parameters can't be added and Spring would not ignore the "id" parameter?
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.
Is there a way to get information about HTTP request from the method down the callstack of the Spring request handler method?
In other words given I have a handler method like:
#GetMapping("/hello")
public String hello(#RequestParam(value = "name", defaultValue = "World") String name) {
MyInternalClass.doSomeAction();
return String.format("Hello %s!", name);
}
I am looking for means to get the information about HTTP request (such as URL, headers, etc.) within the code of the doSomeAction() static method in the MyInternalClass class.
The constraint is that I cannot modify the original method (hello()).
You can add a Request parameter of type HttpServletRequest
#GetMapping("/hello")
public String hello(
#RequestParam(value = "name", defaultValue = "World") String name,
HttpServletRequest originalRequest) {
// HERE: call another method here
return String.format("Hello %s!", name);
}
Have a look at the Spring Reference Documentation, Chapter "Method Arguments"
Part 2
However, I was looking for a method that does not force developers to change their code. I will try to add an example to my question, so it will be more verbose.
You can use the RequestContextHolder to get the request attributes.
HttpServletRequest request =
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes())
.getRequest();
RequestContextHolder.getRequestAttributes() is a static method, that can be invoked from every where (even for a class that is no Spring Bean). But it is required that it is invoked from a thread that was triggert by a HTTP 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.
I am facing a strange issue.
This is my REST API mapping
#RequestMapping(
value = { "/{email}" },
method = RequestMethod.GET,
params = "time")
public void getEmail(
#PathVariable("email") final String sender,
#RequestParam(value = "time", required = true) final long time)
When I call API like this
/someone#someone.com?time=10
I observe that sender contains someone#someone instead of someone#someone.com.
When I give it like this
#RequestMapping(
value = { "/{email:.+}" },
method = RequestMethod.GET,
params = "time")
public void getEmail(
#PathVariable("email") final String sender,
#RequestParam(value = "time", required = true) final long time)
I get 406 error.
I tried this too.
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="useSuffixPatternMatch" value="false" />
</bean>
Still no help.
What am I doing wrong?
Excerpt from RequestMapping JavaDoc:
#PathVariable annotated parameters (Servlet-only) for access to URI
template values (i.e. /hotels/{hotel}). Variable values will be
converted to the declared method argument type. By default, the URI
template will match against the regular expression [^.]* (i.e. any
character other than period), but this can be changed by specifying
another regular expression, like so: /hotels/{hotel:\d+}.
Additionally, #PathVariable can be used on a Map to
gain access to all URI template variables.
Your second mapping definition is correct and works. You haven't probably tested it correctly.
I have mapped one of my method in one Controller to return JSON object by #ResponseBody.
#RequestMapping("/{module}/get/{docId}")
public #ResponseBody Map<String, ? extends Object> get(#PathVariable String module,
#PathVariable String docId) {
Criteria criteria = new Criteria("_id", docId);
return genericDAO.getUniqueEntity(module, true, criteria);
}
However, it redirects me to the JSTLView instead. Say, if the {module} is product and {docId} is 2, then in the console I found:
DispatcherServlet with name 'xxx' processing POST request for [/xxx/product/get/2]
Rendering view [org.springframework.web.servlet.view.JstlView: name 'product/get/2'; URL [/WEB-INF/views/jsp/product/get/2.jsp]] in DispatcherServlet with name 'xxx'
How can that be happened? In the same Controller, I have another method similar to this but it's running fine:
#RequestMapping("/{module}/list")
public #ResponseBody Map<String, ? extends Object> list(#PathVariable String module,
#RequestParam MultiValueMap<String, String> params,
#RequestParam(value = "page", required = false) Integer pageNumber,
#RequestParam(value = "rows", required = false) Integer recordPerPage) {
...
return genericDAO.list(module, criterias, orders, pageNumber, recordPerPage);
}
Above do returns correctly providing me a list of objects I required.
Anyone to help me solve the mystery?
If a controller method returns null, Spring interprets that as saying that you want the framework to render the "default view".
It would be better, I think, that when the method is #RequestBody-annotated, this logic should not apply, but perhaps that's hard to implement - how would it handle a null return from a method that normally returns XML, for example?
Anyway, to stop this from happening, you need to make sure you return something, like an empty Map.