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.
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 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?
I'm trying to make an app's REST API more RESTful and it feels like I'm not using the Spring RequestMappings in the way intended.
I have a single GET end point for doing reads:
#RequestMapping(value = "thing/{thingName}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThing(
#PathVariable(value = "thingName", required = false)
String thingName,
#RequestParam(value = "findByComponent", required = false)
String findByComponentQuery,
#AuthenticationPrincipal User user) {
...
To be more restful, I want this endpoint to do both:
GET /thing/{thingName} returns a single thing having that name
GET /thing or /thing/ with query params returns lists of things
So in my controller, I can test whether {thingName} is null or zero-length and if so, check the query params for known query names.
However calling this with http://localhost:8080/thing/?findByComponent=component123 returns a 404 from Spring with this logging:
12:45:18.485 PageNotFound : No mapping found for HTTP request with URI [/thing/] in DispatcherServlet with name 'dispatcher' : WARN : XNIO-1 task-3 : org.springframework.web.servlet.DispatcherServlet
Spring does not allow path variables ({thingName}) to be mapped to an empty String. In practice, this means that the URL /thing/?findByComponent=component123 does not map to thing/{thingName} with an empty {thingName}, but rather, it expects there to be some mapping for thing. Since there is no endpoint that maps to the path thing (without the path variable), a 404 error is returned.
To solve this issue, you can break this single endpoint into two separate endpoints:
#RequestMapping(value = "thing/{thingName}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThing(
#PathVariable(value = "thingName") String thingName,
#AuthenticationPrincipal User user) {
// ...
}
#RequestMapping(value = "thing",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThings(,
#RequestParam(value = "findByComponent", required = false) String findByComponentQuery,
#AuthenticationPrincipal User user) {
// ...
}
For more information, see With Spring 3.0, can I make an optional path variable?.
The required=false flag allows for two types of requests:
/thing
/thing/<some_value>
Strictly speaking, including a trailing slash at the end of the URL (i.e. /thing/) means that a decision was made to include a value for the path variable, but none was provided. In the context of REST APIs, /thing and /thing/ are two different endpoints, where the latter means that a value after the trailing slash was expected.
A workaround for not having to create three separate request mappings (one for each case above) is to set the #RequestMapping value for the controller to the base path and then have a "" and "/{thingName} request mapping for the two endpoints:
#RestController
#RequestMapping("thing")
public class ThingController {
#RequestMapping(value = "/{thingName}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThing(
#PathVariable(value = "thingName") String thingName) {
return "foo";
}
#RequestMapping(value = "",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThings(
#RequestParam(value = "findByComponent", required = false) String findByComponentQuery) {
return "bar";
}
}
In this case, the following mappings will occur:
/thing: getThings
/thing/: getThings
/thing/foo: getThing
An example of this workaround, including test cases can be found here.
We are using Swagger to generate our API interfaces and model classes. However, Swagger generates the request parameters of our endpoints in a camelCase style instead of snake_case as it specified in our APIs.
For example, the code generated is:
#RequestMapping(value = "/search/test",method = RequestMethod.GET)
public ResponseEntity<Location> getLocation(#RequestParam(value = "locationId",required = true) locationID)
when it should be:
#RequestMapping(value = "/search/test",method = RequestMethod.GET)
public ResponseEntity<Location> getLocation(#RequestParam(value = "location_id",required = true) locationID)
Is there any way to match programmatically (maybe using a HttpConverter) a request containing the param "location_id" with the method containing "locationId" without throwing a PathNotFound exception?
All our params are even Integer or String.
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.