-2

Spring boot rest how to provide an end point which has one path variable which takes different types of values

@Autowired
private UserRepository userRepository;

@RequestMapping(value="/user/{idOrName}", method=RequestMethod.GET)
public User findUser(@PathVariable String idOrName) {
  this.userRepository.findByIdOrName(idOrName, idOrName);
}

public interface UserRepository extends MongoRepository<User, Long> {
  User findByIdOrName(Long id, String name);
}

@Data
@Document(collection="user")
public class User {
  private Long Id;
  private String name;
}

Problem Statement: As you can see from the User model, Id is typeof Long and Name is typeof String. Now I need to implement single end point which offers facility to search user by id or name.

How can I implement rest end point which is having one path variable which either accept id or name which are of different type?

My problem is Not sure how to define @PathVariable when user sends either String or Long. Is it even possible to do? or should I make @PathVariable as String and parse Long?

Dorado
  • 411
  • 2
  • 15
Pratap A.K
  • 4,337
  • 11
  • 42
  • 79
  • Possible duplicate of [What is the meaning of {id:.+} in a Spring MVC requestmapping handler method?](https://stackoverflow.com/questions/20527321/what-is-the-meaning-of-id-in-a-spring-mvc-requestmapping-handler-method) . I found this possible duplicate, but you could search google for "Spring @PathVariable regular expression matching". – GPI Sep 10 '18 at 15:46
  • @GPI my question is different. I am wondering how findByIdOrName works in spring data when Id is Long and Name is String. How can I even pass parameters to this method? – Pratap A.K Sep 10 '18 at 16:00
  • 1
    I'm suggesting you try mapping 2 controler methods, one with a @RequestMapping like `/user/{id:\d+}`validating that the input is only made of digits, and another one validating that there is at least a non digit, e.g. `@RequestMapping("/user/{id:.*\D.*}")`. This way 2 request mappings, two methods, but a single HTTP route exists. – GPI Sep 10 '18 at 16:02
  • There are couple of answers in this thread to address your question. If your question has been answered, please make sure to accept an answer for further references. – Dorado Sep 12 '18 at 14:48
  • @Dorado none of the answer talks about how findByIdOrName works? So I am waiting. findByIdOrName takes 2 parametes not one. Just wondering whether findByIdOrName is possible or not when Id and Name are different types (Eg: Long and String) – Pratap A.K Sep 17 '18 at 06:59
  • @PratapA.K You should take some time to read the documentation at Spring as it provides detailed definition and examples about `@PathVariable` more than I could assemble in this post. – Dorado Sep 17 '18 at 14:16

2 Answers2

2

Short answer. You can't mix different types into a single @PathVariable as Spring says so on their documentation.

According to Spring IO documentation about @PathVariable:

A @PathVariable argument can be of any simple type such as int, long, Date, etc. Spring automatically converts to the appropriate type or throws a TypeMismatchException if it fails to do so.

Note the documentation outlines A @PathVariable argument as singular object type that is not interchangeable into different types.

In addition, it's perfectly normal to have exception handling as answered in another stackoverflow question that trying to determine Long from String without try/catch isn't even worth the work and degrades performance.

You could use a single @PathVariable and have try/catch block to capture NumberFormatException or simply a regular expression.

Method 1: Exception Handling

@RequestMapping(value="/user/{identifier}", method=RequestMethod.GET)
public User findUser(@PathVariable("identifier") String identifier) {
    try {
        Long long_identifier = Long.parseLong(identifier);
        this.userRepository.findByIdOrName(long_identifier);
    } catch(NumberFormatException nfe) {
        //We know it's a plain string by now.
        this.userRepository.findByIdOrName(identifier);
    }
}

Method 2: Regular Expression

Note: if value is not found, the query should simply return empty result.

@RequestMapping(value="/user/{identifier}", method=RequestMethod.GET)
public User findUser(@PathVariable("identifier") String identifier) {
    //Check for digits only
    String regexpr = "\\d+";
    this.userRepository.findByIdOrName(
        identifier.matches(regexpr)
        ? Long.parseLong(identifier)
        : identifier
    );
}

Method 3: Request Mappings with Regular Expression

@RequestMapping(value="/user/{identifier:[A-Za-z]+}", method=RequestMethod.GET)
public User findUserByString(@PathVariable("identifier") String identifier) {
    this.userRepository.findByIdOrName(identifier);
}

@RequestMapping(value="/user/{identifier:[0-9]+}", method=RequestMethod.GET)
public User findUserByLong(@PathVariable("identifier") Long identifier) {
    this.userRepository.findByIdOrName(identifier);
}
Dorado
  • 411
  • 2
  • 15
  • I find the argument about the "normality" of exception handling debatable. If you "expect" a Long, then try/catch/exception makes sense because a string would be an "unexpected" situation. Here the question states that is is *expected* that the input might be a non-number. If the code is expected to throw, then, we are using exceptions as control flow (if/else), and that is not generally a pattern we follow. As I say, debatable. https://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why – GPI Sep 11 '18 at 07:30
  • @GPI Your comment is debatable. Exception is not anti-pattern unless the application *entirely* depend on exceptions for control flow. Most web application, in fact, are prone to exception of some kind, and I have not seen any large-scale web application that produces no exception at all. I think Method 3 could be a good approach to handle requests to a *single* URL and ease with maintenance if both methods are implemented in same controller. – Dorado Sep 11 '18 at 15:02
  • 1
    In my view, option 3 is the best, although I would carrefully craft the "non number" regular expression differently (eg `.*\D.*` or the like), or check if Spring actually falls back if I provide none at all. I'm not trying to argue about exceptions, I agree there is a grey area. I lean on the side of "they are exception all over the place (timeouts, SQL constraints, input validation, ....), but I prefer to have those for things that should not happen, and have if/else control the business logic". Not to mention Exception are heavy objects (stack trace allocation !). – GPI Sep 11 '18 at 16:11
0

Given this is a search, another reasonable approach is to use query parameters:

/users?name=jdoe

or

/users?id=123

And use multiple @QueryParameter instead of @PathParameter.

And consider creating aliases which do use @PathParamter such as:

/users/name/{name}
/users/id/{id}

Clients could use the query parameters approach or the alias, whichever is easier. findUser()can simply delegate to findById() or findByName() depending on which query parameter was provided.

Andrew S
  • 2,509
  • 1
  • 12
  • 14
  • Thanks for the answer. Calling findById or findByName depending variable type is fine, was wondering how does findByIdOrName() works when Id and Name are of different types – Pratap A.K Sep 10 '18 at 16:31