Skip to content

Field Type Annotations

These annotations control how individual fields are rendered and what input widget is used in edit mode.


Target: FIELD

Sets the input widget type for a field. Mateu infers a default stereotype from the Java type (e.g. String → text input, boolean → checkbox), but @Stereotype overrides that inference.

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Stereotype {
FieldStereotype value();
}
AttributeTypeDescription
valueFieldStereotypeThe widget stereotype to apply
ValueDescription
regularDefault text input
radioRadio button group
checkboxCheckbox input
textareaMulti-line text area
toggleToggle switch
comboboxCombo box (typed input with dropdown)
selectDropdown select
emailEmail input
passwordPassword input (masked)
richTextRich text / WYSIWYG editor. The value is HTML. A value stored by the editor of earlier versions (Quill Delta JSON) still opens, and is saved as HTML on the next edit
listBoxList box (scrollable options)
htmlRaw HTML display
markdownMarkdown editor / renderer
imageImage display (<img> from a URL / data-URI value)
uploadableImageImage preview + upload (replace) + delete actions — see @UploadableImage
signatureDrawing canvas that stores the accepted strokes as a PNG data URI — see @Signature
cameraDevice camera with live preview; the shot lands as a JPEG data URI — see @PhotoCapture
treeSelectA select whose dropdown unfolds a TREE of options — see @TreeSelect
iconIcon picker
linkHyperlink
moneyCurrency amount
gridEmbedded data grid
colorColor picker
choiceChoice selector
popoverPopover trigger
sliderRange slider
buttonButton
starsStar rating

From the Products demo:

@Stereotype(FieldStereotype.textarea)
@HiddenInList
String description;

Field stereotypes — email, password, textarea, toggle, radio and slider


No attributes. Shorthand for @Stereotype(FieldStereotype.radio). Renders an enum or options field as a radio button group instead of a dropdown.

public @interface UseRadioButtons {}
public class OrderForm {
@UseRadioButtons
DeliveryMethod delivery;
}

Target: FIELD

No attributes. Shorthand for @Stereotype(FieldStereotype.uploadableImage). Renders a String field as an uploadable image: the image preview combined with an Upload (or Replace) action and a Delete action.

public @interface UploadableImage {}

The picked file is read client-side into a data URI (base64) and stored as the field value, so the image travels in the string itself — no upload endpoint is required. The value may also be a plain image URL. Delete clears the value; pressing your form’s action round-trips the value (the data URI or URL) to the backend like any other string.

@UI("/profile")
public class Profile {
String name;
@UploadableImage
@Label("Avatar")
String avatar; // null/empty → "upload" placeholder; a data-URI/URL → preview + replace + delete
@Toolbar
Object save() {
return Message.success("Saved");
}
}

In read-only mode the field shows just the image (same as @Stereotype(FieldStereotype.image)).


Target: FIELD

Renders a String field as a signature capture: a drawing canvas (mouse or touch) with Clear and Accept. Accepting stores the strokes as a PNG data URI in the field value — the same self-contained contract as @UploadableImage, no upload endpoint involved. An existing value shows as the signature image with Sign again / Delete actions, and the read-only rendering shows the image.

@Signature
@Label("Firma del huésped")
String signature;

Target: FIELD

Renders a String field as a photo capture: opens the device camera (getUserMedia) with a live preview and a shutter; the shot is stored as a JPEG data URI in the field value. When the camera is unavailable (no device, permission denied, insecure context) the widget offers a file input with capture, which on phones opens the native camera. Same self-contained round-trip as @UploadableImage.

@PhotoCapture
@Label("Foto del documento")
String documentPhoto;

Target: FIELD

Sets the minimum value for a slider field. Used together with @Stereotype(FieldStereotype.slider) and @SliderMax.

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SliderMin {
int value();
}
AttributeTypeDescription
valueintMinimum value of the slider range

Target: FIELD

Sets the maximum value for a slider field. Used together with @Stereotype(FieldStereotype.slider) and @SliderMin.

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SliderMax {
int value();
}
AttributeTypeDescription
valueintMaximum value of the slider range
@Stereotype(FieldStereotype.slider)
@SliderMin(0)
@SliderMax(100)
int progress;

A field of type io.mateu.uidl.data.File[] is automatically rendered as an upload widget — no annotation needed. Mateu infers dataType = file from the field type.

import io.mateu.uidl.data.File;
public class ContractForm {
File[] documents; // → upload widget, stores { id, name } per file
@Button
Object save() {
for (File f : documents) {
persist(f.id(), f.name());
}
return Message.success("Saved");
}
}

The upload widget POSTs each file to the fixed path POST /upload. Your application must expose that endpoint; it must return the file identifier as plain text. See File Upload for the full guide including Spring Boot, Micronaut, and Quarkus examples.


Target: FIELD

Marks a field so the UI renders a “Search” button next to it. Clicking the button opens a modal containing the class referenced by selector() — typically a Listing that also implements Selector. When the user picks a row the modal closes and the field is populated with the selected id; the label() supplier provides the human-readable display text.

Use @Searchable instead of @Lookup when the selection screen needs filters, sortable columns, row actions, or even CRUD capabilities — anything more complex than a simple dropdown.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Searchable {
Class<? extends Selector> selector() default Selector.class;
Class<? extends LookupLabelSupplier> label() default LookupLabelSupplier.class;
boolean bubble() default false;
boolean editableCode() default false;
boolean showCode() default false;
}
AttributeTypeDefaultDescription
selectorClass<? extends Selector>Selector.classScreen opened in the modal. Must implement Selector<IdType>. Typically also implements Listing.
labelClass<? extends LookupLabelSupplier>LookupLabelSupplier.classResolves the display text for a stored id.
bubblebooleanfalsePropagates the selection event to the parent component.
editableCodebooleanfalseAllows the user to type the code/id directly in the field.
showCodebooleanfalseShows the raw id alongside the resolved label.

The selector class must:

  1. Implement Selector<IdType> — selected() is called when the user clicks a row and must return a SelectedItem containing the id and a label.
  2. Optionally implement LookupLabelSupplier — resolves a stored id back to its display text (reused in label()).
  3. Typically implement Listing<Row> (plus Searchable, and Filterable<Filters> for a filter bar) to get a full filterable, pageable table inside the modal.
@Trigger(type = TriggerType.OnLoad, actionId = "search")
@Style("min-width: 40rem;")
public class HotelSelector implements Listing<Row>, Searchable,
Selector<String>, LookupLabelSupplier {
String _fieldId;
@Override
public String fieldId() { return _fieldId; }
@Override
public Selector withFieldId(String fieldId) {
_fieldId = fieldId;
return this;
}
@Override
public ListingData<Row> search(SearchRequest request, HttpRequest httpRequest) {
return ListingData.of(
rows.stream()
.filter(r -> r.name().contains(request.searchText()))
.toList()
);
}
@Override
public SelectedItem<String> selected(HttpRequest httpRequest) {
Row row = httpRequest.getClickedRow(rowClass());
return new SelectedItem<>(row.id(), row.name());
}
@Override
public String label(String fieldName, Object id, HttpRequest httpRequest) {
return rows.stream()
.filter(r -> r.id().equals(id))
.findFirst().orElseThrow().name();
}
}
public class BookingForm {
@Searchable(selector = HotelSelector.class, label = HotelSelector.class)
@NotEmpty
String hotelId;
@Button
Object save() {
return Message.success("Saved " + hotelId);
}
}

Multi-valued fields: List, Set and arrays of ids

Section titled “Multi-valued fields: List, Set and arrays of ids”

The same annotation works on a field that holds several ids — a List<IdType>, a Set<IdType> or an IdType[] (String, Long/Integer, UUID…). Nothing else changes in the declaration: multi-selection is inferred from the field type, and the same selector serves both kinds of field.

public class CampaignForm {
// one hotel
@Searchable(selector = HotelSelector.class, label = HotelSelector.class)
String hotelId;
// several hotels: chips + «Add»
@Searchable(selector = HotelSelector.class, label = HotelSelector.class)
List<String> hotelIds = new ArrayList<>();
@Searchable(selector = ChannelSelector.class)
Set<Long> channels;
@Searchable(selector = ContractSelector.class)
UUID[] contracts;
}

How a multi-valued @Searchable behaves (Vaadin and Redwood alike):

  • The current ids render as chips, each labelled through label() (falling back to the id when there is no label), with a ✕ to remove it, plus an «Add» button.
  • «Add» opens the selector in the modal with row selection enabled and an «Add selected» button: the checked rows are added to the field — appended in order, without duplicates (and with Set semantics for a Set) — and the modal closes. Clicking a row adds just that row.
  • Removing a chip removes the id (client-side; it travels with the next action). In a read-only view the field shows the labels only.
  • Validation is the usual one (@NotEmpty, @Size…, checked on the server); the ids bind back into the List, Set or array with the element type converted (JSON numbers and strings into Long, strings into UUID…).

The labels travel in the component data: <field>-label holds the display text (for a multi-valued field, the labels joined by ", "), and <field>-labels holds {id → label} for the chips. When label() is not given, the view model labels the ids if it is a LookupLabelSupplier, and otherwise the selector does, if it is one.

Selector has a default method for multi-selection:

public interface Selector<IdType> {
SelectedItem<IdType> selected(HttpRequest httpRequest); // the clicked row
default List<SelectedItem<IdType>> selectedItems(HttpRequest httpRequest) {
// each checked row mapped through selected(), as if it were the clicked row
}
String fieldId();
Selector withFieldId(String name);
}

The default presents each checked row to selected() as the clicked row, so a selector written for single-valued fields — one that reads httpRequest.getClickedRow(...), like HotelSelector above — serves multi-valued fields with no extra code. Override selectedItems only when the checked rows need a different mapping:

@Override
public List<SelectedItem<Long>> selectedItems(HttpRequest httpRequest) {
return httpRequest.getSelectedRows(ChannelRow.class).stream()
.map(row -> new SelectedItem<>(row.id(), row.name()))
.toList();
}

The merging happens on the server: the modal carries the ids the field held when it opened, and the selector answers the merged value, its labels and the close of the modal.

@Lookup@Searchable
UI widgetIncremental-search dropdown (inline)Text display + “Search” button → modal (chips + «Add» for a List/Set/array of ids)
Selector classLookupOptionsSupplier (list of Option)Listing + Selector (full screen)
Suitable forSimple option lists, fast lookupsComplex grids with filters, actions, or CRUD