232

Is it possible for a Spring controller to handle both kind of requests?

1) http://localhost:8080/submit/id/ID123432?logout=true

2) http://localhost:8080/submit/id/ID123432?name=sam&password=543432

If I define a single controller of the kind:

 @RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,   
 produces="text/xml")
public String showLoginWindow(@PathVariable("id") String id,
                              @RequestParam(value = "logout", required = false) String logout,
                              @RequestParam("name") String username,
                              @RequestParam("password") String password,
                              @ModelAttribute("submitModel") SubmitModel model,
                              BindingResult errors) throws LoginException {...}

the HTTP request with "logout" is not accepted.

If I define two controllers to handle each request separately, Spring complains with the exception "There is already 'Controller' bean method ... mapped".

1
  • 2
    Read this article: codeflex.co/… Commented Mar 29, 2018 at 11:08

4 Answers 4

290

Before Java 8 and Spring 5 (but works with Java 8+ and Spring 5+ too)

You need to give required = false for name and password request parameters as well. That's because, when you provide just the logout parameter, it actually expects for name and password because they are still "implicitly" mandatory.

It worked when you just gave name and password because logout wasn't a mandatory parameter thanks to required = false already given for logout.

Update for Java 8 and Spring 5 (and above)

You can now use the Optional class from Java 8 onwards to make the parameters optional.

@RequestMapping (value = "/path", method = RequestMethod.GET)
public String handleRequest(@RequestParam("paramName") Optional<String> variableName) {
    String paramValue = variableName.orElse("");
    // use the paramValue
}
Sign up to request clarification or add additional context in comments.

2 Comments

Why use Optional when @RequestParam has the defaultValue attribute? For instance: T myHandler (@RequestParam(value = "paramName", defaultValue = "myDefaultValue") String variableName) { }. By doing so there's no need to use Optional nor declare another paramValue variable
@AlexandreRocha - There is a difference between both the approaches and the use case should define what approach is used. Use Optional Parameters: When the parameter is truly optional and the absence of it should trigger different behavior. In OP's case, if logout parameter is not present, then the operation is login/sign-in. Use Default Values: When you want to ensure that there's always a value for the parameter, and the client should not be required to supply it. For example, setting a default page size for pagination. Therefore, the use of Optional makes more sense here.
195

As part of Spring 4.1.1 onwards you now have full support of Java 8 Optional (original ticket) therefore in your example both requests will go via your single mapping endpoint as long as you replace required=false with Optional for your 3 params logout, name, password:

@RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,   
 produces="text/xml")
public String showLoginWindow(@PathVariable("id") String id,
                              @RequestParam(value = "logout") Optional<String> logout,
                              @RequestParam("name") Optional<String> username,
                              @RequestParam("password") Optional<String> password,
                              @ModelAttribute("submitModel") SubmitModel model,
                              BindingResult errors) throws LoginException {...}

4 Comments

@VibhavChaddha you can use something like this: if (idOfUser.isPresent()){ System.out.println("idOfUser: "+ idOfUser.get()); }
Intellij warning: 'Optional<Long>' used as type for parameter 'requestedTimelineStart' less... (Strg+F1) Inspection info: Reports any uses of java.util.Optional<T>, java.util.OptionalDouble, java.util.OptionalInt, java.util.OptionalLong or com.google.common.base.Optional as the type for a field or a parameter. Optional was designed to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result". Using a field with type java.util.Optional is also problematic if the class needs to be Serializable, which java.util.Optional is not.
This should be the new correct answer with Java 8 I believe.
@PeMa it's a controversial warning. Personally I think it's much clearer to use Optional as parameters instead of null checks. Yes, parameters can be null itself, but returned Optional value can also be null. Especially in this case when Spring handles null parameters internally when wrapping it in Optional.
43

Create 2 methods which handle the cases. You can instruct the @RequestMapping annotation to take into account certain parameters whilst mapping the request. That way you can nicely split this into 2 methods.

@RequestMapping (value="/submit/id/{id}", method=RequestMethod.GET, 
                 produces="text/xml", params={"logout"})
public String handleLogout(@PathVariable("id") String id, 
        @RequestParam("logout") String logout) { ... }

@RequestMapping (value="/submit/id/{id}", method=RequestMethod.GET, 
                 produces="text/xml", params={"name", "password"})
public String handleLogin(@PathVariable("id") String id, @RequestParam("name") 
        String username, @RequestParam("password") String password, 
        @ModelAttribute("submitModel") SubmitModel model, BindingResult errors) 
        throws LoginException {...}

3 Comments

what will happen when someone passes both logout, name and password to the URL ? Just read the documentation, it says !myParam style expressions indicate that the * specified parameter is not supposed to be present in the request. got to try.
It will find the best match, it probably will try to use the handleLogin else it will give an exception stating no mapping can be found.
Just one note: from the security perspective logout should only accept POST requests, so there should be 2 methods and it doesn't make any sense to keep their URL the same then.
1

In case someone is looking for mapping Optional parameters with Pojo, same can be done like below.

@RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,   
 produces="text/xml")
public String showLoginWindow(@PathVariable("id") String id,
                              LoginRequest loginRequest,
                              @ModelAttribute("submitModel") SubmitModel model,
                              BindingResult errors) throws LoginException {...}
@Data
@NoArgsConstructor
//@AllArgsConstructor - Don't use this
public class LoginRequest {
    private Optional<String> logout = Optional.empty();
    private Optional<String> username = Optional.empty();
    private Optional<String> password = Optional.empty();
}

Note: Do not use @AllArgsConstructor on POJO else it will initialize the fields as null.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.