6.19 Handling Form Data

To make data binding model customizations consistent between form data and JSON, Micronaut uses Jackson to implement binding data from form submissions.

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

In practice this means that to bind regular form data, the only change required to the previous JSON binding code is updating the MediaType consumed:

Binding Form Data to POJOs

  1. @Controller("/people")
  2. public class PersonController {
  3. Map<String, Person> inMemoryDatastore = new ConcurrentHashMap<>();
  4. @Post
  5. public HttpResponse<Person> save(@Body Person person) {
  6. inMemoryDatastore.put(person.getFirstName(), person);
  7. return HttpResponse.created(person);
  8. }
  9. }

Binding Form Data to POJOs

  1. @Controller("/people")
  2. class PersonController {
  3. Map<String, Person> inMemoryDatastore = new ConcurrentHashMap<>()
  4. @Post
  5. HttpResponse<Person> save(@Body Person person) {
  6. inMemoryDatastore.put(person.getFirstName(), person)
  7. HttpResponse.created(person)
  8. }
  9. }

Binding Form Data to POJOs

  1. @Controller("/people")
  2. class PersonController {
  3. internal var inMemoryDatastore: MutableMap<String, Person> = ConcurrentHashMap()
  4. @Post
  5. fun save(@Body person: Person): HttpResponse<Person> {
  6. inMemoryDatastore[person.firstName] = person
  7. return HttpResponse.created(person)
  8. }
  9. }
To avoid denial of service attacks, collection types and arrays created during binding are limited by the setting jackson.arraySizeThreshold in application.yml

Alternatively, instead of using a POJO you can bind form data directly to method parameters (which works with JSON too!):

Binding Form Data to Parameters

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

Binding Form Data to Parameters

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

Binding Form Data to Parameters

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

As you can see from the example above, this approach lets you 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.