Documentation

§Handling form submission

Before you start with Play forms, read the documentation on the Play enhancer. The Play enhancer generates accessors for fields in Java classes for you, so that you don’t have to generate them yourself. You may decide to use this as a convenience. All the examples below show manually writing accessors for your classes.

§Defining a form

The play.data package contains several helpers to handle HTTP form data submission and validation. The easiest way to handle a form submission is to define a play.data.Form that wraps an existing class:

public class User {

    protected String email;
    protected String password;

    public void setEmail(String email) {
        this.email = email;
    }

    public String getEmail() {
        return email;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword() {
        return password;
    }

}

To wrap a class you have to inject a play.data.FormFactory into your Controller which then allows you to create the form:

Form<User> userForm = formFactory.form(User.class);

Note: The underlying binding is done using Spring data binder.

This form can generate a User result value from HashMap<String,String> data:

Map<String,String> anyData = new HashMap();
anyData.put("email", "bob@gmail.com");
anyData.put("password", "secret");

User user = userForm.bind(anyData).get();

If you have a request available in the scope, you can bind directly from the request content:

User user = userForm.bindFromRequest().get();

§Defining constraints

You can define additional constraints that will be checked during the binding phase using JSR-303 (Bean Validation) annotations:

public class User {

    @Required
    protected String email;
    protected String password;

    public void setEmail(String email) {
        this.email = email;
    }

    public String getEmail() {
        return email;
    }

    public void setPassword(String password) {
    	this.password = password;
    }

    public String getPassword() {
        return password;
    }

}

Tip: The play.data.validation.Constraints class contains several built-in validation annotations.

You can also define an ad-hoc validation by adding a validate method to your top object:

public class User {

    @Constraints.Required
    protected String email;
    protected String password;

    public String validate() {
        if (authenticate(email, password) == null) {
            return "Invalid email or password";
        }
        return null;
    }

    public void setEmail(String email) {
    	this.email = email;
    }

    public String getEmail() {
    	return email;
    }

    public void setPassword(String password) {
    	this.password = password;
    }

    public String getPassword() {
        return password;
    }

}

The message returned in the above example will become a global error.

The validate-method can return the following types: String, List<ValidationError> or Map<String,List<ValidationError>>

validate method is called after checking annotation-based constraints and only if they pass. If validation passes you must return null . Returning any non-null value (empty string or empty list) is treated as failed validation.

List<ValidationError> may be useful when you have additional validations for fields. For example:

public List<ValidationError> validate() {
    List<ValidationError> errors = new ArrayList<ValidationError>();
    if (User.byEmail(email) != null) {
        errors.add(new ValidationError("email", "This e-mail is already registered."));
    }
    return errors.isEmpty() ? null : errors;
}

Using Map<String,List<ValidationError>> is similar to List<ValidationError> where map’s keys are error codes similar to email in the example above.

§Handling binding failure

Of course if you can define constraints, then you need to be able to handle the binding errors.

if (userForm.hasErrors()) {
    return badRequest(views.html.form.render(userForm));
} else {
    User user = userForm.get();
    return ok("Got user " + user);
}

Typically, as shown above, the form simply gets passed to a template. Global errors can be rendered in the following way:

@if(form.hasGlobalErrors) {
    <p class="error">
        @for(error <- form.globalErrors) {
            <p>@Messages(error.messages, error.arguments.toArray: _*)</p>
        }
    </p>
}

Errors for a particular field can be rendered in the following manner:

@for(error <- form("email").errors) {
    <p>@Messages(error.messages, error.arguments.toArray: _*)</p>
}

§Filling a form with initial default values

Sometimes you’ll want to fill a form with existing values, typically for editing:

userForm = userForm.fill(new User("bob@gmail.com", "secret"));

Tip: Form objects are immutable - calls to methods like bind() and fill() will return a new object filled with the new data.

You can use a DynamicForm if you need to retrieve data from an html form that is not related to a Model:

public Result hello() {
    DynamicForm requestData = formFactory.form().bindFromRequest();
    String firstname = requestData.get("firstname");
    String lastname = requestData.get("lastname");
    return ok("Hello " + firstname + " " + lastname);
}

§Register a custom DataBinder

In case you want to define a mapping from a custom object to a form field string and vice versa you need to register a new Formatter for this object.
You can achieve this by registering a provider for Formatters which will do the proper initialization.
For an object like JodaTime’s LocalTime it could look like this:

import java.text.ParseException;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;

import org.joda.time.LocalTime;

import play.data.format.Formatters;
import play.data.format.Formatters.SimpleFormatter;
import play.i18n.MessagesApi;


@Singleton
public class FormattersProvider implements Provider<Formatters> {

    private final MessagesApi messagesApi;

    @Inject
    public FormattersProvider(MessagesApi messagesApi) {
        this.messagesApi = messagesApi;
    }

    @Override
    public Formatters get() {
        Formatters formatters = new Formatters(messagesApi);

        formatters.register(LocalTime.class, new SimpleFormatter<LocalTime>() {

            private Pattern timePattern = Pattern.compile(
                    "([012]?\\d)(?:[\\s:\\._\\-]+([0-5]\\d))?"
            );

            @Override
            public LocalTime parse(String input, Locale l) throws ParseException {
                Matcher m = timePattern.matcher(input);
                if (!m.find()) throw new ParseException("No valid Input", 0);
                int hour = Integer.valueOf(m.group(1));
                int min = m.group(2) == null ? 0 : Integer.valueOf(m.group(2));
                return new LocalTime(hour, min);
            }

            @Override
            public String print(LocalTime localTime, Locale l) {
                return localTime.toString("HH:mm");
            }

        });

        return formatters;
    }
}

After defining the provider you have to bind it:

import com.google.inject.AbstractModule;

import play.data.format.Formatters;

public class FormattersModule extends AbstractModule {

    @Override
    protected void configure() {

        bind(Formatters.class).toProvider(FormattersProvider.class);

    }
}

Finally you have to disable Play’s default FormattersModule and instead enable your module in application.conf:

play.modules.enabled += "com.example.FormattersModule"
play.modules.disabled += "play.data.format.FormattersModule"

When the binding fails an array of errors keys is created, the first one defined in the messages file will be used. This array will generally contain:

["error.invalid.<fieldName>", "error.invalid.<type>", "error.invalid"]

The errors keys are created by Spring DefaultMessageCodesResolver, the root “typeMismatch” is replaced by “error.invalid”.

Next: Using the form template helpers


Found an error in this documentation? The source code for this page can be found here. After reading the documentation guidelines, please feel free to contribute a pull request.