I've started working with RestEasy and I've come across a problem that I can't seem to find an answer to. If I have 2 methods that both resolve to the same path (in this case /path1/path2/path3), but they both have a different number of query parameters, will RestEasy be able to determine which method to use?
#GET
#NoCache
#Produces({
MediaType.APPLICATION_JSON
})
#Path("/path1/path2/{path3}")
public String getResults1(
#PathParam("path3") String path3,
#QueryParam("query1") #DefaultValue("") String query1,
#QueryParam("query2") String query2,
#QueryParam("query3") #DefaultValue("25") int query3) {
...
}
#GET
#NoCache
#Produces({
MediaType.APPLICATION_JSON
})
#Path("/path1/path2/{path3}")
public String getResults2(
#PathParam("path3") String path3,
#QueryParam("query1") #DefaultValue("") String query1,
#QueryParam("query2") #DefaultValue("5") Integer query2) {
...
}
I've done some testing and yesterday it seemed that everything was working perfectly and that it could choose the right path, but now today I'm starting to see it take the wrong path each time.
Is this something that should be handled, or should I just suck it up and put it in 1 method and do the check myself?
No, you should be handling this in the method. If conflicting resources are found it is implementation independent which method will be matched.
Take a look at your example again:
If you submitted query1 and query2 how would it know if you wanted the method with 2 query parameters or the method with 3 query parameters and you wanted it to default the 3rd to it's default value?
Related
This question already has answers here:
Design RESTful query API with a long list of query parameters [closed]
(4 answers)
Closed 3 years ago.
I am designing a rest API, to get a resource based on some parameters but in some cases these parameters are between 15-20 in number.
I am thinking of using a POST request to get the resource based on these 15 parameters. I know that POST request should not be used in case of getting the resource.
I want to know if there is a better option to handle this then sending POST request?
You can use Get service by using Map. It will accept all param.
/test?A=ABC&B=123
#RequestMapping(value="/test",method = RequestMethod.GET)
public String testUrl(#RequestParam Map<String, String> parameters)
{
println(parameters.get("A"));
println(parameters.get("B"));
return parameters.get("A");
}
Out Put Will Be
ABC
123
GET doesn't restrict the number of parameters
the only restriction is the length of the URL (which contains these parameters)
So if you're expecting that the parameters and values would cause a long URL, you can use POST instead
Standard says that URL length should be not more than 2,083 characters
even if some browsers/servers allow more, it's better to stick on this value for a wide-range support for all browsers/servers/gateways
In order to make your #Controller code more concise (e.g. get rid of 15x #RequestParam) you can use #ModelAttribute annotation.
#GetMapping(value="/things")
public List<Thing> findAllThings(#ModelAttribute ThingDTO thing) {
// logic
}
and your ThingDTO class like that:
public class ThingDTO {
private String name;
private ThingType type;
[...] //getters, setters etc.
}
This is going to map your class attributes to the #RequestParams. The code looks a bit cleaner imho.
When it comes to the URL length you should check the topic here: What is the maximum length of a URL in different browsers? and decide if there's possibility of exceeding the limit.
What should you use? For data retrieval I'd in 99% cases go with GET so if the above is not a blocker for you, go with GET. If there's a chance of exceeding the limit, go with POST.
The parameter length shouldn't be a problem to handle for the server.
You should read about how to design a rest api. I would recommend you to build it like this:
/api/{version}/{entity}/{id}
If you are using Spring Boot this is very simple to build.
Just write a Controller like this
#RestController
#RequestMapping("/api/users")
public class UsersService {
#Autowired
UsersRepository userRepo;
#RequestMapping(value="/find-all", method=RequestMethod.GET)
public Page<User> getAllUsers(#RequestParam(value="page", defaultValue = "0", required=false)Integer page,
#RequestParam(value="size", defaultValue= "20", required=false)Integer size){
...
}
#RequestMapping(value="/find/{id}")
public ResponseEntity<User> getUserById(#PathVariable(name="id")Long id){
...
}
#RequestMapping(value="/save", method=RequestMethod.POST)
public ResponseEntity<User> createUser(#RequestBody User user){
...
}
#RequestMapping(value="/delete/{id}", method = RequestMethod.GET)
public ResponseEntity<Void> deleteUser(#PathVariable(name="id")Long id){
...
}
}
Hope this sample code helps you to build your api. Just pass your ID as a PathVariable like shown in deleteUser() or findUser().
If you need more Parameters, you can extend the list or use RequestParam like used in getAllUsers().
The parameters are optional but need a default value.
I'm trying to create a GET request, where I have two different requests. Since both resources are related with each other, I'm trying to put them under one 'sub-resource'.
The first one is:
#QueryParam("{customerId}")
public List<customerModel> getCustomer(#QueryParam("customerId") Long customerId) {....}
this fetches the customer's name depending on the customerId
#QueryParam("{customerId}/details")
public List<customerDetailModel> getCustomerDetail(#QueryParam("customerId") Long customerId) {....}
this fetches the detailed information of the customer (phone number, address, etc.)
I'm running the first one with the following (works fine) :
......?customerId=7453112
but I can't reach the second request when I'm hitting the following URL:
......?customerId=7453112/details
Any suggestions?
Resource methods must be annotated with #Path and with a request method designator such as #GET, #POST, #PUT,
#DELETE, #HEAD or #OPTIONS. Your #QueryParam annotation is misplaced.
By the way, for this situation, you should consider using a path parameter instead of a query parameter. Hence you should use #PathParam instead of #QueryParam in the method parameter. For more details on when to use query or path parameters, have a look at this answer
Also, not sure why you are returning a List if you are requesting a resource by its unique identifier. In this situation, you are supposed to return a representation of a single resource (and not a represention of multiple resources).
So your resource class would be as following:
#Path("customers")
#Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public class CustomerResource {
#GET
#Path("{customerId}")
public CustomerModel getCustomer(#PathParam("customerId") Long id) {
...
}
#GET
#Path("{customerId}/details")
public CustomerDetailModel getCustomerDetail(#PathParam("customerId") Long id) {
...
}
}
The first endpoint can be requested as following:
GET http://example.com/api/customers/1
And the second endpoint can be requested as following:
GET http://example.com/api/customers/1/details
You need to go with #RequestMapping.
Something like:
#RequestMapping("/{customerId}") and #RequestMapping("/{customerId}/details").
Your URL becomes 7453112/details instead of query params.
You can specify the /details in an #Path annotation, and then use the same query parameter, like this:
#Path("/details/{customerId}")
public List<customerDetailModel> getCustomerDetail(#PathParam("customerId") Long customerId) {....}
Then your URL would look like this:
.../details/7453112
Or if you want to keep using it as a query parameter you can do something like this:
#Path("/details")
public List<customerDetailModel> getCustomerDetail(#QueryParam("customerId") Long customerId) {....}
using this url:
.../details?customerId=xxx
Using JAX-RS, I have the following 3 #Paths.
#Path(JobRest.PATH)
#Api(value = JobRest.PATH, description = "REST APIs for Jobs")
public interface JobRest {
public static final String PATH = "/job";
#GET
#Path("/last")
#Produces(MediaType.APPLICATION_JSON)
public Job retrieveLastJob(...);
#GET
#Path("/{jobId}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Job retrieveJob(...., #PathParam("jobId") String jobId, );
#GET
#Produces(MediaType.APPLICATION_JSON)
public JobList retrieveAllJobs(....);
}
/job correctly calls retrieveAllJobs()
/job/1236 correctly calls retrieveJob(..., "1236", ...).
I expected that /job/last would call retrieveLastJob(...), since it matches, but it calls retrieveJob(..., "last", ...) instead.
How do I change the notation so that /job/last will call retrieveLastJob(...)?
TL;DR
Remove the #Consumes(MediaType.APPLICATION_JSON) on the retrieveJob method. For one, it does not accept a body, so it does not consume anything. Secondly it conflicts with the expected behavior.
I've tested with both Jersey and RESTeasy and it seems to be a difference in implementation. Jersey works fine with your code, while RESTeasy always hits the retrieveJob method, as you are experiencing.
Here's my take. If you look at the JAX-RS spec; 3.7.2 Request Matching, there's a semi-cryptic algorithm for matching resources, that goes something like this.
Get all matching resource class (by path), put them into a set.
Get all matching resource methods (by path), put them into a set.
Sort the methods by best matching path (most literal characters go first).
Sort by media type (with consumes and produces).
From my perspective, in this particular case, after step 3, the retrieveLastJob should automatically win, as it has the most literal characters. The producing media types are the same, and the consumes media type should not even matter, since it is a GET request with no Content-Type to do any matching.
My guess it RESTeasy still uses the annotation to sort even though it should not even be taken into consideration in this case. So it would appear that the method with the annotation is given more precedence, as it appears to be more specific, by way of just having an annotation, while the other does not. But that (step 4) level of specificity really shouldn't matter in this case.
I don't know if it's a bug against the spec. It's not too clear on how it should be handled, but I personally think the Jersey behavior is the correct behavior, for the simple fact that this level of specificity should not matter in this particular case. In any case, it is not correct to have the #Consumes annotation anyway for a GET request with no body.
I am creating two methods(GET) in a REST service in which the URL is for the first method is of the form
/a/b/{parameter}?start=1 &
end=100 & name="some value"
and for the second method it is
/a/b/{parameter}
When i run the code it gives a conflict.
Can anyone please suggest me how these can be configured for the methods and also to make the query paramters OPTIONAL?
Thanks
This should work fine:
#GET
#Path("/a/b/{parameter}")
public Response get(#PathParam("parameter") String parameter, #QueryParam("start") Integer start, #QueryParam("end") Integer end, #QueryParam("name") String name) {
// switch here according to the values received. All are optional and non-sent are nulls here
}
If in the future you will have default values you can add them inline like this (for the name query param for example):
#DefaultValue("some-name") #QueryParam("name") String name
I am building rest webservice using jersey, when I add this function i got this exception, but when i remove it, the server works very good.
Customer.orderWeb(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String), should not consume any form parameter.
the code is
#Path("orderWeb/{customerID}/{restaurantID}/{IDs}/{numbers}/{descriptions}/{addressID}")
#GET
#Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN,
MediaType.TEXT_HTML, MediaType.TEXT_XML })
#Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_PLAIN,
MediaType.TEXT_PLAIN })
public String orderWeb(#FormParam("customerID") String customerID,
#FormParam("restaurantID") String restaurantID,
#FormParam("IDs") String IDs, #FormParam("numbers") String numbers,
#FormParam("descriptions") String descriptions,
#FormParam("addressID") String customerAddress) {
return "WSSSSSSSSSSSSSSSSSS";
}
it is weird because I always use path like that path. i dont know what am i doing wrong
Use #QueryParam with #GET and #FormParam with #POST
Not sure what you got fixed from the approved answer, but since I'm not seeing the answer FYI you should use #PathParam to capture path segments (not #FormParam which is to capture POST data)
if you are using #FormParam. Also make sure INPUT HTML types are using name= not id=.