0

With a simple Spring Boot application, I am trying to understand the difference between the Spring Boot annotations @RequestParam and @RequestBody and their respective use. The Spring documentation defines @RequestParam as an 'Annotation which indicates that a method parameter should be bound to a web request parameter' and @RequestBody as an ‘Annotation which indicates that a method parameter should be bound to the web request body.’ It is thus fair to assume that both annotations and associated methods can be used as equifinal alternatives (different methods for similar results) to pass parameters to a backend application, for example to insert the values ‘email', 'username’ and ‘password’ in a User database (just forgetting for the simplicity about the necessary encryption of the password).

Therefore, one could expect that the code snipped below

@PostMapping
public User save(@RequestBody User user) {
    return userService.createUser(user);
}

be equivalent to

@PostMapping(value = "/users")
public ResponseEntity<User> createUser(
        @RequestParam  String email,
        @RequestParam String username,
        @RequestParam  String password

) {
    try {

        User user = new User();
        user.setEmail(email);
        user.setUsername(username);
        user.setPassword(password);

        userService.createUser(user);
        return ResponseEntity.noContent().build();

    } catch (Exception exception) {
        return new ResponseEntity<>(HttpStatus.I_AM_A_TEAPOT);
    }
}

Trying the @RequestBody alternative with Postman results in a 200 OK response:

200 ok

But trying the @RequestParam alternative results in a 400 ‘bad request’ error:

400 bad request

Why does this @RequestParam approach triggers an error while the apparently equivalent approach @RequestBody works as expected?

For the completeness, the user model is given below:

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String email;
    private String username;
    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}

The repository is given below:

public interface UserRepository extends JpaRepository<User, Long> {}

The user service interface is given below:

public interface UserService {
    List<User> getAllUsers();
    User getUserById(Long id);
    User createUser(User user);
    void deleteUser(Long id);
}

and the user service is given below:

@Service
public class UserServiceImpl implements UserService {
    private UserRepository userRepository;

    @Autowired
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    @Override
    public User createUser(User user) {
        return userRepository.save(user);
    }

    @Override
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }

    @Override
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

Michael
  • 41,989
  • 11
  • 82
  • 128
Christophe
  • 11
  • 2
  • 1
    Hi Michael, sorry for that, it should be clear now – Christophe Jul 06 '21 at 10:40
  • Is your compiler configured to include parameter names in the class files? javac default is to discard them. The argument to include them is [`-parameters`](https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html). Without that flag, Spring can't map each param to the incoming query param names. If you're unsure, you can pass the name explicitly to each one `@RequestParam(name="email")`. – Michael Jul 06 '21 at 10:53
  • By the way, the quotes around your query parameter values in postman are superfluous, and I think you'll end up with the quotes going into your database as part of the string. Shouldn't make the request fail though – Michael Jul 06 '21 at 10:55
  • What did you change on the settings tab in Postman? There is a green dot there. There should also be a more detailed error in your log. – Vlad L Jul 06 '21 at 11:12
  • Thanks for these good suggestions. However passing the names explicitly for example @RequestParam(name = "email") String email did not fix the issue. Note that the error is now a 405 'Method not allowed' – Christophe Jul 06 '21 at 11:30
  • @VladL the postman settings when trying to pass the parameters through the url are the same as when trying to pass them through the body. – Christophe Jul 06 '21 at 11:34
  • Why don’t you just check the log? – Vlad L Jul 06 '21 at 19:09

3 Answers3

0

The two approaches are equivalent in functionality, if not in the expected usage.

You are not meant to use the quotes around your parameter values. If you remove the " then it should work, at least it does on a new Spring Boot project that I copied your code into + a recent version of Postman.

Postman also has an option on the "Settings" tab to encode the url automatically. If you enable that option then the quotes will be encoded and will be part of your parameter values. It is enabled by default but on your screenshot there is a green dot there indicating you changed something. My guess is there is a setting there that you set which is causing the bad request (likely disabled encoding of the url).

When I disable encoding I get this error in the log:

java.lang.IllegalArgumentException:
Invalid character found in the request target 
[/users?email=bla&username=hello&password="bla" ]. 
The valid characters are defined in RFC 7230 and RFC 3986
Vlad L
  • 1,544
  • 3
  • 6
  • 20
  • Thanks a lot Vlad L for having tested the code and for these suggestions. In postman, url encoding is now enabled, quotes around parameters are removed, green dot is absent now, but still a 405 error triggerd. However, your tests and suggestions should bring us close to the solution... – Christophe Jul 06 '21 at 16:30
0

First of all, thanks for your support and the time you spent on testing the code! Both @requestParam and @requestBody can indeed be used as two alternatives to pass values to the backend, as asserted in the preceding answer (thanks!). An explanation was still needed why it was not the case using the code provided in my question, since modifying the request in postman as indicated did not help and using cUrl to post the request resulted in exactly the same 405 error. So the solution had to be found in the code. It took a while to find it, but there was a @RequestMapping(value="/users") annotation on top of my controller class which had to be disabled. Note that apparently, the incorrect use of @RequestMapping only interfered with @RequestParam but not with @RequestBody. The following code below now works (@GetMapping and @deleteMapping omitted here) and gives exactly the same results as the @RequestBody which was given in the question.

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

    @Autowired
    private UserServiceImpl userService;

    @PostMapping(value = "/users")
    public ResponseEntity<Object> createUser(
            @RequestParam(name = "email") String email,
            @RequestParam(name = "username") String username,
            @RequestParam(name = "password") String password
   ) {
        try {
            User user = new User();
            user.setEmail(email);
            user.setUsername(username);
            user.setPassword(password);
            userService.createUser(user);
            return ResponseEntity.noContent().build();
        } catch (Exception exception) {
            return new ResponseEntity<>(HttpStatus.I_AM_A_TEAPOT);
        }
    }
Christophe
  • 11
  • 2
-1

The way u r passing request param values in url for @RequestParam is not correct. For multiple params, u can pass the values like below:

http://<url>:<port>/user/101?param1=10&param2=20

And also refer the below answers:

How to put RequestParam value as URL

Pass multiple parameters to rest API - Spring

Boney
  • 1,462
  • 1
  • 11
  • 19
  • Not correct how? Looks fine to me except for the extraneous quotes around the values, but those shouldn't make the request fail – Michael Jul 06 '21 at 10:54
  • The way the parameters are passed in Postman is http://localhost:8080/users?email="fake@email.com"&username="fakename"&password="123456" so this should be correct isn't it? – Christophe Jul 06 '21 at 11:01