@Post

Annotate methods with @Post for HTTP POST web routes.

Post JSON

A method with @Post is by default expecting a JSON body.

In the example below customer is populated via body content expected to be JSON (ctx.bodyAsClass().

@Post
void save(Customer customer) {
  ...
}

The generated code for the above is:

ApiBuilder.post("/customers", ctx -> {
  ctx.status(201);
  Customer customer = ctx.bodyAsClass(Customer.class);
  controller.save(customer);
});

@Form (Simple form)

If the method additionally has @Form then the method parameters default to be form parameters.

In the following example name, email and url all default to be form parameters.

@Form @Post("register")
void register(String name, String email, String url) {
  ...
}

@FormParam

For the example above we could alternatively use @FormParam on each of the form parameters rather than @Form. We then get:

@Post("register")
void register(@FormParam String name, @FormParam String email, @FormParam String url) {
  ...
}

The expectation is that we most often would use @Form because it reduces "annotation noise" and that we will very rarely use @FormParam. Potentially we only use @FormParam if the parameter name is snake case or similar that doesn't match a valid Java/Kotlin identifier.

The generated code for both cases above is the same:

ApiBuilder.post("/customers/register", ctx -> {
  ctx.status(201);
  String name = ctx.formParam("name");
  String email = ctx.formParam("email");
  String url = ctx.formParam("url");
  controller.register(name, email, url);
});

@Form with "Form Beans" (Large forms)

In the case where we are posting a form with a lot of parameters we can define a bean with properties for each of the form parameters (rather than have a method with lots of arguments).

"Form beans" currently must have a default no-arg constructor and bean properties (accessible setters or fields).

Using a form bean can make the code nicer (method signature) and gives us a nicer option to use validation annotations on the "form bean" properties.

public class MyForm {

  @Size(min=2, max=100)
  public String name;
  public String email;
  public String url;
}
@Form @Post("register")
void register(MyForm myForm) {
  ...
}
The generated code for the above is.
ApiBuilder.post("/contacts/register", ctx -> {
  ctx.status(201);
  MyForm myForm =  new MyForm();
  myForm.name = ctx.formParam("name");
  myForm.email = ctx.formParam("email");
  myForm.url = ctx.formParam("url");

  controller.register(myForm);
});

"Form beans" are nice with forms with lots of properties because they declutter our code and the generated code takes care of putting the values into our bean properties so that we don't have to write that code.

This use of @Form is very similar to JAX-RS @BeanParam except that the bean properties default be being form parameters. With JAX-RS we would put a @FormParam on every property that is a form parameter (which becomes noise on a large form).

Kotlin data class

Kotlin data classes are a natural fit for form beans.

data class SaveForm(var id: Long, var name: String, var someDate: LocalDate?)


@Form @Post
fun saveIt(form: SaveForm) {

  ...
}

The generated code for the above controller method is:

ApiBuilder.post("/", ctx -> {
  ctx.status(201);
  SaveForm form =  new SaveForm(
    asLong(checkNull(ctx.formParam("id"), "id")),     // non-nullable type
    checkNull(ctx.formParam("name"), "name"),         // non-nullable type
    toLocalDate(ctx.formParam("someDate"))
  );

  controller.saveIt(form);
});

If the form bean has Kotlin non-nullable types (id and name above) then the generated code includes a null check when populating the bean (the checkNull() method).

If there is not a value for a non-nullable Kotlin property then a validation error will be thrown at that point (this validation exception is thrown relatively early compared to using bean validation on Java form beans).

Form beans with @QueryParam, @Header, @Cookie properties

The properties on a "form bean" default to being form parameters. We put @QueryParam, @Header or @Cookie on properties that are instead query params, headers or cookies.

public class MyForm {

  @Size(min=2, max=100)
  public String name;
  public String email;
  public String url;

  @QueryParam
  public Boolean overrideFlag;

  @Header
  public String ifModifiedSince;

  @Cookie
  public String myState;
}

The generated code populates from query params, headers and cookies. The generated code is:

ApiBuilder.post("/contacts/register", ctx -> {
  ctx.status(201);
  MyForm myForm =  new MyForm();
  myForm.name = ctx.formParam("name");
  myForm.email = ctx.formParam("email");
  myForm.url = ctx.formParam("url");
  myForm.overrideFlag = toBoolean(ctx.queryParam("overrideFlag"));     // queryParam !!
  myForm.ifModifiedSince = ctx.header("If-Modified-Since");            // header !!
  myForm.myState = ctx.cookie("myState");                              // cookie !!

  controller.register(myForm);
});