Difference between #PathVariable, #RequestParam, and #RequestBody - java

I understand what the #PathVariable, #RequestParam and #RequestBody does in Spring, but not clear on which scenarios we have to use them as they are used for extracting value from the URI. Why we have to send data like localhost:8080/getBooks/time and localhost:8080/getBooks?book=time.

Example 1:
#RequestParam is used mainly for filtering purposes
Lets say you want to get George Martin's book:
GET localhost:8080/books?author=georgemartin
Here we pass author=georgemartin as request parameter. This will supposedly get all of Martin's books, example game of thrones series.
This will be used mainly for GET operation.
Example 2:
#PathVariable is used mainly for getting individual objects or piece of data
Lets say you want to get a book by its id:
GET localhost:8080/books/1
Here we pass 1 as path variable. This will supposedly get the 1 book with id 1, example first part of game of thrones' book.
This will be used mainly for DELETE/GET operation.
Example 3:
#RequestBody is used mainly for saving object(s)(or piece of data)
Lets say you want to add a book:
POST localhost:8080/books/
With request body having following attributes:
{
"author":"George Martin",
"Book":"Game of thrones"
...
...
}
This will add a book to the db. This would be used mainly for PUT/POST operation.
Note: never use verb naming for endpoint, instead, use plural nouns. So books/ is ideal instead of getbooks/.
Reference/Read more:
https://stackoverflow.blog/2020/03/02/best-practices-for-rest-api-design/#h-use-nouns-instead-of-verbs-in-endpoint-paths
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestBody.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestParam.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/PathVariable.html

#PathVariable is for parts of the path (i.e. /person/{id})
#RequestParam is for the GET query parameters (i.e. /person?name="Bob").
#RequestBody is for the actual body of a request.

#RequestBody used with POST Verb whereas
#RequestParam and #pathVariable used with GET Verb
#RequstParam : It extract the value from query string
used for fitering,sorting and pagination
In Request Param the values can be encrypted
localhost:8080/getBooks?start=1&end=100
#pathVariable : It extract value from URI Path
In Path variable the value cannot be encoded
Its used get the data based on the value
Reference:
https://www.baeldung.com/spring-requestparam-vs-pathvariable

Related

How to pass list of parameters in rest get call (like filter object in eCommerce )

In my application, there is a requirement of getting data based on some parameters.
I just want to what is the better way to do.
The one way is, I can pass the list of parameters as a path variable.
The second way is, I can pass the request body, I think it is vague and I am not sure it is possible or not.
You can find the code below:
#GetMapping("/cities/{cityName}/latitude/{latitude}/longitude/{longitude}/cityId/{cityId}/street/{street}")
public ResponseEntity<ResponseContainer<CityDto>> getCityByCityNameOrLatitudeAndLongitude() {
}
I just want to know how can I achieve the same.
There is one more question, E-commerce companies have big filter criteria so how they are achieving.
Although there is no hard & fast rule but I generally avoid sending a body in GET request because it's a bad design. You should also refer to this SO Post which contains discussion about using body in GET request. It's an opinionated post and there is no clear YES or NO, but you will get an idea.
HTTP GET with request body
You can either use Path params or query params depending on what those field represent.
Regarding the difference or which to use when I am quoting this answer, which mentions that although there is no hard rule but generally it's better to use params which can uniquely identify the resource as Path param (e.g. id, name etc) and if your param is supposed to do something like filtering/sorting e.g. records after Jan 1 2019 , then go for query param.
Also personally in one of my APIs (which performs filtering), I am using a generic query param, where I pass on JSON object in my query. So basically my API needs to search an object based on variable/multiple attributes. E.g. I have in my db , objects which have certain voltage, current, size etc. values. So, request might come with a combination of 1 or more. So to keep my API flexible, I have provided a query param which can accept JSON object.
So I make a request like this:
{{SERVER}}/api/search?query={voltage:12V,size:10}
And in my API, I can convert this json object to corresponding POJO:
#GET
#Path("/search")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response search(#QueryParam("query") String queryParam) throws Exception
{
Myobj obj = new Gson().fromJson(queryParam, Myobj.class);
// rest of code
By passing the parameters in the path, you are restricting yourself to extend your API. If you want to extend your API, for example, if you want to filter with criteria as Street1 (or) Street2 then your path wouldnot support it and it will force you to update your API. It is better to pass criteria objects in the body or url parameter. Amazon India is passing criteria like below. I have choosen mobiles with criteria as Manufacturer = Samsung or MI, Storage as 8gb or 4gb and they simply appended the criteria in the query parameters.
There is a third way, Request Params.
#GetMapping
public ResponseEntity<ResponseContainer<CityDto>> getCityByCityNameOrLatitudeAndLongitude(#RequestParam("cityName") String cityName, #RequestParam("latitude") String latitude, #RequestParam("longitude") String longitude){
// Your code
}
For more: 16.3.3.3 Binding request parameters to method parameters with #RequestParam
Parameters using this annotation are required by default, but you can specify that a parameter is optional by setting #RequestParam's required attribute to false (e.g., #RequestParam(value="id", required=false)).
https://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/htmlsingle/spring-framework-reference.html#mvc-ann-requestparam

GraphQL java: Throwing exceptions when request has unused variables

Let's say I have a request payload with a variables map and a query. Does graphql-java have any out-of-the-box mechanisms that I can use to detect unused variables in the payload?
For example:
type Query { hello : String! } # this just resolves to "hello world"
When the user sends in {foo: 42} as the variables in the request payload, I want to throw an error (sayIllegalArgumentException).
Edit: Answers/comments on how to implement this without any out-of-the-box mechanisms are also welcome.
Before executing the query , use Parser to parse the query into Document which is an object representation of the query.The variables defined in the query can be accessed by looking its OperationDefinition --> VariableDefinition instance. Then comparing them with the variables in the request payload.
As a bonus ,as the graphql-java engine will parse and validate the query for each query execution and this process can be somewhat time consuming, to avoid parsing and validate it twice, you can use PreparsedDocumentProvider

What is the best way to get one param from form (Play2 Framework)?

I am want to create simple form for searching records via one parameter (for example, name).
Seems like creating a class with one property (name) and than use helpers for forms - is not a best way.
Is there any examles how can I get POST data from request and fetch property value from that data?
Thanks a lot for wasting your time.
You already answered your own question, I just want to provide some more information:
You are right about creating a class with one single property, however keep in mind that you can use validation annotations (like #Required, #Email, etc.) in this class - so if there is some (super) complex logic behind this property this might also be a valuable option.
The second solution would be to use DynamicForms - you use them when you don't really have a model that is backing up the submission form. It goes like this:
public static Result index() {
DynamicForm requestData = Form.form().bindFromRequest();
String name = requestData.get("name");
return ok(name);
}
And of course the third option to get the values is like you mentioned:
String name = request().body().asFormUrlEncoded().get("name")[0];
If you do not what to use form validation, I don't think you need to create a class. Instead, you can use AJAX function like $.ajax(), that will be route to your specific controller function. Moreover, you can call your model function from your controller then at last return the result. The result will be caught by the $.ajax() function.
$.ajax
type: "POST"
url: url
data: data
success: success
dataType: dataType

Spring, middle score in request parameter

Is there a way to map a query parameter with a middle score using requests in spring?
I have no problem binding single worded parameters doing this:
Uri example: http://localhost:8080/test/?product=hotels
public class CitiesRequest{
private ProductType product;
public ProductType getProduct() {
return this.product;
}
public void setProduct(String product) {
this.product = product;
}
}
But I'd like to be able to receive parameters like this:
http://localhost:8080/test/?product-type=hotels
As Misha stated it is syntactically incorrect to have a variable name with a hyphen in Java. But Spring is fine with that and allows you to specify a parameter name (in the request) different from the variable name (in java code). For exemple, when using RequestMapping driven controller, one can write :
#RequestMapping("/test")
public ModelAndView getProduct(
#RequestParam("product-type") String productType) {
...
}
That way, getProduct will be called for a url like http://localhost/test?product-type=hotels and the parameter productTypewill receive the value hotels. And all is still purely declarative.
By default, Spring maps the query parameter key to the name of the Java variable. However, it's syntactically incorrect to have a variable name with a hyphen in Java, which explains why you're finding it particularly difficult to get Spring to set the parameter's value for you.
One workaround that might work is to just have a Map<String, String[]> parameter to represent all of the parameters. Then Spring doesn't have to map any query parameters to variable names, so the hyphenated name might end up in that map of all parameters. It may not be as comfortable as pre-split parameter objects, but it might get the hyphenated keys.
Another solution might be to configure the WebDataBinder, which controls how data from HTTP requests are mapped onto your controller's request parameters. But that's a whole can of worms, especially if you're just starting out with Spring. You can read more about it in the documentation under "data binding".

Understanding How Spring MVC's #RequestMapping POST Works

I have a simple Controller that looks like this:-
#Controller
#RequestMapping(value = "/groups")
public class GroupsController {
// mapping #1
#RequestMapping(method = RequestMethod.GET)
public String main(#ModelAttribute GroupForm groupForm, Model model) {
...
}
// mapping #2
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String changeGroup(#PathVariable Long id, #ModelAttribute GroupForm groupForm, Model model) {
...
}
// mapping #3
#RequestMapping(method = RequestMethod.POST)
public String save(#Valid #ModelAttribute GroupForm groupForm, BindingResult bindingResult, Model model) {
...
}
}
Basically, this page has the following functionalities:-
User visits main page (/groups GET).
User creates a new group (/groups POST) or selects a specific group (/groups/1 GET).
User edits an existing group (/groups/1 POST).
I understand how both GET request mappings work here. Mapping #2 is defined, otherwise (/groups/1 GET) will cause a "No mapping found" exception.
What I'm trying to understand here is why mapping #3 handles both (/groups POST) and (/groups/1 POST)? It makes sense that it should handle (/groups POST) here since the request mapping matches the URI. Why (/groups/1 POST) isn't causing a "No mapping found" exception being thrown here? In fact, it almost seems like any POST with URI beginning with /groups (ex: /groups/bla/1 POST) will also be handled by mapping #3.
Can someone provide a clear explanation of this to me? Thanks much.
CLARIFICATION
I understand the fact that I can use more appropriate methods (like GET, POST, PUT or DELETE)... or I can create yet another request mapping to handle /groups/{id} POST.
However, what I want to really know is...
.... "Why does mapping #3 handle /groups/1 POST too?"
The "closest match" reasoning don't seem to hold true because if I remove mapping #2, then I would think mapping #1 will handle /groups/1 GET, but it doesn't and it causes a "No mapping found" exception.
I'm just a little stumped here.
This is complicated, I think it is better to read the code.
In Spring 3.0 The magic is done by method public Method resolveHandlerMethod(HttpServletRequest request) of the inner class ServletHandlerMethodResolver of org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.
An instance of this class exists for every Request Controller Class, and has a field handlerMethods that contains a list of all the request methods.
But let me summarize how I understand it
Spring first checks if at least one handler method matches (this can contain false negatives)
Then it creates a map of all really matching handler methods
Then it sorts the map by request path: RequestSpecificMappingInfoComparator
and takes the first one
The sorting works this way: the RequestSpecificMappingInfoComparator first compares the path with the help of an AntPathMatcher, if two methods are equal according to this, then other metrics (like number of parameters, number of headers, etc.) are taken into account with respect to the request.
Spring tries to find the mapping which matches the closest. Hence, in your case of any POST request, the only map found for the request type is Mapping# 3.
Neither of Mapping 1 or Mapping 2 matches your request type, and hence are ignored.
May be you can try removing the Mapping #3, and see that Spring throws a runtime error since it does not find a match!
I would add a PUT mapping for /groups/{id}. I guess POST would work too but not strictly correct from a HTTP perspective.
adding #RequestMapping("/{id}", POST) should cover it?
add #PathVariable to the Long id parameter in mapping #2

Categories