The advantage of this approach is that the same Jackson annotations used for customizing JSON binding can be used for form submissions too.

    What this means in practise is that in order to bind regular form data the only change required to the previous JSON binding code is updating the consumed:

    Binding Form Data to POJOs

    1. class PersonController {
    2. ConcurrentHashMap<String, Person> inMemoryDatastore = [:]
    3. @Post
    4. HttpResponse<Person> save(@Body Person person) {
    5. inMemoryDatastore.put(person.getFirstName(), person)
    6. HttpResponse.created(person)
    7. }
    8. }

    Binding Form Data to POJOs

    Binding Form Data to Parameters

    1. public class PersonController {
    2. Map<String, Person> inMemoryDatastore = new ConcurrentHashMap<>();
    3. @Post("/saveWithArgs")
    4. public HttpResponse<Person> save(String firstName, String lastName, Optional<Integer> age) {
    5. Person p = new Person(firstName, lastName);
    6. age.ifPresent(p::setAge);
    7. inMemoryDatastore.put(p.getFirstName(), p);
    8. return HttpResponse.created(p);
    9. }

    Binding Form Data to Parameters

    1. class PersonController {
    2. internal var inMemoryDatastore: MutableMap<String, Person> = ConcurrentHashMap()
    3. @Post("/saveWithArgs")
    4. fun save(firstName: String, lastName: String, age: Optional<Int>): HttpResponse<Person> {
    5. val p = Person(firstName, lastName)
    6. age.ifPresent { p.age = it }
    7. inMemoryDatastore[p.firstName] = p
    8. return HttpResponse.created(p)
    9. }
    10. }

    As you can see from the example above, this approach allows you to use features such as support for Optional types and restrict the parameters to be bound (When using POJOs you must be careful to use Jackson annotations to exclude properties that should not be bound).