State, actions and fields
A Mateu ViewModel is a plain Java class. Its fields are the UI state; its annotated methods are the actions.
State (fields)
Section titled “State (fields)”Each field in a ViewModel maps to a form control in the browser. Mateu infers the control type from the Java type.
public class ProductForm {
String name; // → text field boolean active; // → checkbox Status status; // → combobox (enum) int stockCount; // → number input LocalDate releaseDate; // → date picker
}The field value is hydrated from request data before any action runs and serialized back after the action completes. There is no persistent state between requests.
Field types and default controls
Section titled “Field types and default controls”| Java type | Default control |
|---|---|
String | Text field |
int / long / double | Number input |
boolean | Checkbox |
LocalDate / LocalDateTime | Date / datetime picker |
enum | Combobox |
List<String> | Multi-select |
Overriding the inferred control with @Stereotype
Section titled “Overriding the inferred control with @Stereotype”Use @Stereotype when the inferred control is not what you want.
enum Status { Available, OutOfStock }
@Stereotype(FieldStereotype.radio)Status status; // → radio button group instead of combobox
@Stereotype(FieldStereotype.textarea)String description; // → multi-line text area instead of single-line
@Stereotype(FieldStereotype.richText)String body; // → rich text editor
@Stereotype(FieldStereotype.password)String secret; // → password input (masked)
@Stereotype(FieldStereotype.toggle)boolean active; // → toggle switch instead of checkboxThe full list of available stereotypes is defined in FieldStereotype:
regular, radio, checkbox, textarea, toggle, combobox, select, email, password, richText, listBox, html, markdown, image, icon, link, money, grid, color, choice, popover, slider, button, stars.
Read-only fields
Section titled “Read-only fields”@ReadOnlyString computedValue;Read-only fields are shown in the form but cannot be edited by the user.
Hidden fields
Section titled “Hidden fields”@HiddenString internalId;Hidden fields carry state across requests but are not rendered.
Actions
Section titled “Actions”Actions are public methods that the user can trigger from the browser.
Toolbar actions
Section titled “Toolbar actions”@Toolbar places the action in the toolbar, typically at the top of the screen.
@Toolbarpublic void refresh() { // re-load data from backend}Form actions
Section titled “Form actions”@Button places the action in the form footer area.
@Buttonpublic void save() { productRepository.save(id, name, status);}Inline buttons
Section titled “Inline buttons”A Runnable field annotated with @Button behaves like a regular form field, so it can be positioned inline inside the form layout.
@ButtonRunnable generate = () -> { name = "Generated-" + System.currentTimeMillis();};What actions can return
Section titled “What actions can return”The return value of an action controls what happens in the browser after it completes.
Return nothing — state mutation only
Section titled “Return nothing — state mutation only”@Buttonpublic void normalize() { name = name.trim().toUpperCase();}When an action returns void, Mateu sends no explicit effect. Use State(this) if you want the form to reflect mutated field values.
Return a Message — show feedback
Section titled “Return a Message — show feedback”@Buttonpublic Message check() { if (status == Status.OutOfStock) { return Message.builder() .variant(NotificationVariant.warning) .text("Product is out of stock") .build(); } return new Message("Product is available");}Message shows a toast notification. The default variant is success.
Return State — update the form
Section titled “Return State — update the form”@Buttonpublic State validate() { if (name == null || name.isBlank()) { name = "(unnamed)"; } return new State(this);}State(this) pushes the current ViewModel state back to the frontend, refreshing all form fields.
Return a URI — navigate to a URL
Section titled “Return a URI — navigate to a URL”@Buttonpublic URI create() { String id = productService.create(name, status); return URI.create("/products/" + id);}Return another ViewModel — open a new page
Section titled “Return another ViewModel — open a new page”@Buttonpublic ProductDetail open() { return productService.load(id);}Returning a ViewModel instance tells Mateu to render that object as a new page.
Return a UICommand — browser-level control
Section titled “Return a UICommand — browser-level control”@Toolbarpublic UICommand closeAndRefresh() { return UICommand.navigateTo("/products");}UICommand gives low-level control over browser behavior: navigation, running actions, pushing history state, and more.
Validation
Section titled “Validation”Standard Bean Validation annotations are enforced automatically. They prevent invalid form submissions and display errors next to the affected field.
@NotBlankString name;
@NotNullStatus status;
@Size(max = 200)String description;See Validation for more detail.
Full example
Section titled “Full example”@UI("/products/new")public class NewProductForm {
@NotBlank String name;
@NotNull @Stereotype(FieldStereotype.radio) Status status = Status.Available;
@Stereotype(FieldStereotype.textarea) String description;
@ReadOnly String summary;
@Button Runnable preview = () -> { summary = name + " (" + status + ")"; };
@Button @Action(validationRequired = true) public Object save() { String id = productRepository.save(name, status, description); return List.of( new Message("Product saved"), URI.create("/products/" + id) ); }
}