'Spring RestController - findById and findByEmail request method not working (Ambiguous handler)

I want to retrieve user's info either based on the ID or the Email. Below is the controller class I wrote:

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserDao userDao;
    
    @GetMapping(value = "/{id:\\d+}")
    public User findOneById(@PathVariable("id") final Integer userId) {
        return userDao.findById(userId).get();
    }
    
    @GetMapping(value = "/{email}")
    public User findOneByEmail(@PathVariable("email") final String email) {
        return userDao.findByEmail(email).get();
    }     

The code is not working and giving error
java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/users/12223': {public com.example.persistence.model.User com.example.rest.controller.UserController.findOneById(java.lang.Integer), public com.example.persistence.model.User com.example.rest.controller.UserController.findOneByEmail(java.lang.String)}.

I thought Regex would solve this issue but unfortunately it didn't.
The reason for error I understood, but what is the way to handle such requirement?



Solution 1:[1]

Your problem is that http://localhost:8080/users/12223 matches both /users/{id:\\d+} and /users/{email}. 12223 is a valid parameter for both methods:

  • 12223 matches {id:\\d+} because it has all digits
  • 12223 matches {email} because regex expression is not specified and any parameter will match email.

Spring can't select an appropriate endpoint and gives an error: Ambiguous handler methods mapped for HTTP path.

If you try another parameter, say: http://localhost:8080/users/[email protected], there will be no error. Spring will be able to find out, that [email protected] doesn't match id and matches email.

As JB Nizet mentioned in the comments, you have 2 ways to solve this:

  1. Specify regex for the e-mail to match e-mail format, something like {email:.+@.+\..+}
  2. Clarify endpoints like pDer666 recommended:

    @GetMapping(value = "/email/{email}")

    @GetMapping(value = "/id/{id:\d+}")

Solution 2:[2]

There are different ways to solve this. It is possible to provide two GetMappings with different paths or you use query parameters in only one get request. If the Email is Set you retrieve data by Email If the other is Set retrieve it by the other parameter. With this solution you can easily add more parameters to search by and you have the posibility to query your data by OR or AND without adding a new controller method.

Url : http://localhost:8080/[email protected] OR http://localhost:8080/users?id=1234

@GetMapping
@ResponseBody
public String findOne(@RequestParam("id") long id, @RequestParam("email") String email) {
    ...
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pavel Molchanov
Solution 2