Skip to content

Key annotations

These are the annotations that define most of the Mateu DSL.

This page is intentionally selective: it focuses on the annotations that shape routing, UI structure, actions, relationships, behavior, security, and presentation.


Defines a UI root.

@UI("/products")
public class Products {}

Use it when a class should become a page or UI entry point.

  • value() → route or UI path
  • indexHtmlPath() → frontend entry html
  • frontendComponentPath() → renderer entry path

Defines additional routes and supports placeholders.

@Route("/example/:name")
public class ExamplePage {
String name;
}

Use it when:

  • you need path parameters
  • you want more than one route
  • you want nested routing with parentRoute
  • value() → route pattern
  • uis() → UI roots where the route applies
  • parentRoute() → parent route for composition

Adds a field to the application menu.

@Menu
Users users;

Use it to expose pages, modules or links in app navigation.

  • selected() → mark current option as selected

Marks a field or method as a button/action.

@Button
void save() {}

Use it for form-level actions.

Targets:

  • methods
  • fields

Places a field or method in the toolbar.

@Toolbar
void refresh() {}

Use it for top-level actions, typically in headers or listings.


Configures action behavior.

@Action(
validationRequired = true,
confirmationRequired = true
)

Use it when you need more than a bare button.

  • validation
  • confirmation dialogs
  • background execution
  • SSE
  • browser integration (href, js)
  • modal settings
  • selected-row requirement
  • fields to validate
  • how the CLIENT calls the action: timeoutMillis and idempotent

Behaviour on a slow or unreliable connection

Section titled “Behaviour on a slow or unreliable connection”
// A lookup the user is waiting on: give up quickly so they can retype.
@Action(timeoutMillis = 5000)
public void quickLookup() { … }
// Batch work: a long ceiling, and safe for the client to re-send after a blip.
@Action(timeoutMillis = 120000, idempotent = true)
public Message recalculateTotals() { … }
  • timeoutMillis — request ceiling for this action in ms. 0 (the default) keeps the client’s own 60 s ceiling. One global timeout cannot serve both a type-ahead lookup and a report export.
  • idempotent — declares that re-running the action cannot apply the same change twice, so the client may retry it by itself after a transient failure. Defaults to false: after a timeout the client cannot know whether the server processed the request, so writes are never repeated behind the user’s back. Reads (route loads, searches, lookups) are detected automatically.

See Slow connections for everything the framework does on a bad network without any declaration.


Defines a relationship resolved dynamically by backend suppliers.

@Lookup(
search = PermissionOptionsSupplier.class,
label = PermissionLabelSupplier.class
)
List<String> permissions;

Use it when a field depends on remote or dynamic options.

  • search()LookupOptionsSupplier
  • label()LookupLabelSupplier

Adds a “Search” button to a field. Clicking it opens a modal containing the selector() class — typically a Listing that also implements Selector. When the user selects a row, the modal closes and the field is populated with the chosen id; label() resolves the display text for a stored id.

@Searchable(selector = HotelSelector.class, label = HotelSelector.class)
String hotelId;

Use it when picking the related entity requires a filterable grid, row actions, or CRUD capabilities — anything beyond a simple dropdown (see @Lookup).

  • selector()Selector implementation (usually a Listing subclass) opened in the modal
  • label()LookupLabelSupplier to display the stored id as text
  • editableCode() → allow the user to type the id directly
  • showCode() → show the raw id alongside the label

Overrides the default inferred rendering type of a field.

@Stereotype(FieldStereotype.radio)
Status status;

Use it when the Java type is not enough to express how the field should be rendered.


Renders an enum field as radio buttons instead of the default dropdown, regardless of how many constants the enum has. Equivalent to @Stereotype(FieldStereotype.radio), but self-documenting on the field.

@UseRadioButtons
Weekday deliveryDay;

Under @AutoLayout, enums with up to 4 constants get radio buttons automatically — use @UseRadioButtons to force them on larger enums or on classes without inference.


Lets Mateu infer the UX patterns of a form from the amount and structure of the declared information, so the class only declares the data and Mateu decides how to present it. Inference is deterministic (based on the declared structure, never on runtime data) and only fills the gaps the developer left open — every explicit layout annotation (@Section, @Tab, @Zones, @FoldedLayout, @Toc, @Stereotype, @UseRadioButtons…) always wins.

@UI("/customers/new")
@AutoLayout
public class NewCustomerForm { ... } // just declare the fields

Current rules:

  • Fold optionals — a heavy editable form with no declared grouping keeps its required fields visible and collapses the optional ones into a “More options” panel.
  • Sections → tabs — a read-only view with many substantial sections is presented as tabs (marked adaptable, so renderers may degrade them to an accordion on narrow viewports); a sticky section or an explicit @Toc disables this.
  • Small enums → radio buttons — enums with up to 4 constants render as radios.
  • value()@AutoLayout(false) opts a class out when inference is enabled globally via the mateu.layout.inference system property.

See Layout inference for the full rules, thresholds and wire semantics.


Binds row selection of a grid field (a List field rendered with @Stereotype(FieldStereotype.grid)) to a developer action. When the user clicks a row, Mateu runs the named method on the class that declares the grid field, auto-injecting the selected row as a method parameter (the parameter typed as the row class, via HttpRequest.getClickedRow). Unlike the built-in CRUD detail editing, it works on read-only grids too — making it the natural way to build a master/detail view: select a row, then emit an event or update another part of the screen.

@OnRowSelected("onGuestSelected")
@Stereotype(FieldStereotype.grid)
List<GuestData> guests;
Object onGuestSelected(GuestData guest, HttpRequest httpRequest) {
return UICommand.dispatchEvent("pax-selected", Map.of("name", guest.getFirstName()));
}
  • value — name of the method/action to run when a row is selected.
  • shortcut — optional keyboard-shortcut base (e.g. "ctrl+shift") for selecting a row by position: the base combo plus a digit selects that row (ctrl+shift+1 → first row … ninth; top-row or numeric keypad).

Combine with @SubscribeTo / @Emits to drive other components from a selection. Opt-in: grids without @OnRowSelected keep their default selection behaviour.


Adds inline CSS style to a type, field or parameter.

@Style("max-width:900px;margin:auto;")

Use it for layout constraints and small visual adjustments.


Class-level. Declares a view whose full response — structure and data — never varies per request, user or time (a help page, an “about” screen, a fixed reference screen). The client caches the whole response for the browser session and, on a return visit, renders it from the cache and skips the server round-trip entirely.

@Route("/about")
@Title("About")
@StaticView
public class About { /* ... */ }

It is a promise, like @Action(idempotent = true): only use it when the content genuinely does not change. Do not use it on a screen whose content depends on data, the logged-in user, permissions, time, or ${…} interpolation of live state — the client would keep showing the first rendering for the rest of the session. The skip is session-scoped, so a full page reload always reloads (a new deployment is picked up on the next refresh).

This is the opt-in top layer of Mateu’s client-side caching; the two layers beneath it (instant structure on return, and smaller data-only revalidation) are automatic and need no annotation. Also available as [StaticView] (C#) and @static_view (Python).


Controls form rendering.

@FormLayout(columns = 1)
public class MixedPage {}

Use it when you want to change the default form layout.

  • columns()
  • theme()
  • style()

Defines dynamic browser-side behavior.

@Rule(
filter = "name == null || name == ''",
action = RuleAction.SetAttributeValue,
fieldName = "save",
fieldAttribute = RuleFieldAttribute.disabled,
value = "true",
expression = "",
actionId = "",
result = RuleResult.Continue
)

RuleAction values: SetAttributeValue, SetStateValue, SetDataValue, SetAppStateValue, SetAppDataValue, SetCssClass, SetStyle, RunAction, RunJS. RuleResult is Continue or Stop (stop evaluating further rules once this one matches).

Use it when the UI must change dynamically without a server round-trip.

  • field attributes
  • values
  • styles
  • css classes
  • actions

Defines when an action runs.

@Trigger(type = TriggerType.OnLoad, actionId = "refresh")

Use it when an action should be triggered automatically.

  • OnLoad
  • OnSuccess
  • OnError
  • OnValueChange
  • OnCustomEvent

Subscribes a component to a named custom event emitted by another component (or itself), so components on the same screen can talk to each other. When the event fires, Mateu runs the named action on the subscribing component server-side, passing the event payload as the action parameters. Repeatable at class level.

// React to any "checkin-confirmed" event on the global bus by re-running this component's load.
@SubscribeTo(event = "checkin-confirmed", action = "load")
public class CheckInForm { ... }

Use it together with UICommand.dispatchEvent(...) — one component emits, others react and refresh in place instead of forcing a full navigation.

  • event — name of the custom event to listen for.
  • action — id (method name) of the action to run when the event fires.
  • source — where to listen (SubscriptionSource):
    • DOCUMENT (default) — global event bus: react no matter which component emitted the event.
    • COMPONENT — react only to events emitted by the component named in from.
    • SELF — react only to events bubbling up from this component’s own subtree (the behaviour of a raw @Trigger(type = OnCustomEvent)).
  • from — logical name of the emitting component to match, used only with source = COMPONENT (matches the emitter’s @Emits(name = ...)).
  • condition — optional client-side expression that must hold for the action to run.

A raw @Trigger(type = OnCustomEvent, ...) is equivalent to @SubscribeTo with source = SELF.


Declares the custom events a component emits and, optionally, the logical name it emits them under. It is mostly documentary — events are actually emitted by returning UICommand.dispatchEvent(...) from an action. Its one runtime effect is name: when set, it is stamped into every event the component emits (as detail.__source) so @SubscribeTo(source = COMPONENT, from = ...) subscribers can filter by origin. When omitted, the component’s server-side type acts as the implicit source.

@Emits(events = "checkin-confirmed", name = "guests-section")
public class GuestsSection { ... }
  • events — names of the events this component emits (documentary).
  • name — logical source name stamped into emitted events as detail.__source.

Return a UICommand.dispatchEvent(...) from any action to fire a custom event from that component:

@Toolbar
Object confirmCheckin(HttpRequest httpRequest) {
// ... persist ...
return List.of(
Message.success("Check-in confirmed"),
UICommand.dispatchEvent("checkin-confirmed", Map.of("reservationId", id)));
}

The event bubbles (composed), so DOCUMENT-scoped subscribers anywhere on the page receive it.


Restricts access to parts of the UI.

@EyesOnly(roles = "admin")
@Menu
Users users;

Use it for authorization. It gates menu entries and, on a form field, hides the field when the current user is not authorized. Identity is read from the JWT Bearer token.

  • roles
  • groups
  • scopes
  • permissions

The same four dimensions are used by @ReadOnlyUnless and @DisabledUnless below. Matching is AND across the dimensions you declare, OR within each; no dimension declared → unrestricted.

The browser is untrusted, so these restrictions are not merely visual. On every request, when Mateu hydrates the ViewModel from the incoming component state, it drops any state entry targeting a field whose @EyesOnly or @ReadOnlyUnless restriction the request does not satisfy — a tampered component state cannot write a field the UI hides or locks (the Mateu equivalent of mass-assignment protection). The field keeps its server-side value instead.

Consequence: never rely on the round-tripped state for a protected field’s value — compute it server-side (initializer, load(), or the action itself). For an unauthorized user the client copy is never read back.


Renders a field (or, at class level, the whole view) read-only unless the current user matches one of the declared identity dimensions — same dimensions and matching as @EyesOnly.

@ReadOnlyUnless(roles = "manager")
BigDecimal salary; // read-only for everyone except managers

Composes with @EyesOnly for layered access:

@EyesOnly(roles = "staff") // hidden unless staff
@ReadOnlyUnless(roles = "manager") // read-only unless manager
BigDecimal salary; // non-staff: hidden · staff: read-only · manager: editable

Renders a field or a button (@Button / @Toolbar) disabled unless the current user matches one of the declared identity dimensions — same dimensions and matching as @EyesOnly. Disabled state is carried as a client-side rule; the authorization is decided server-side.

@DisabledUnless(scopes = "write")
String note;
@Toolbar
@DisabledUnless(roles = "approver")
Object approve(HttpRequest req) { ... }

Secures a UI using Keycloak.

@UI("")
@KeycloakSecured(
url = "https://auth-server",
realm = "mateu",
clientId = "demo"
)
public class App {}

Use it for application authentication.

  • url()
  • realm()
  • clientId()
  • jsUrl()

Switches a Crud (or AutoCrud) to a split-panel layout: the record list stays on the left and the create/edit form opens on the right. No page transition occurs when the user selects a row.

@UI("/orders")
@SplitCrud
public class OrdersCrud extends AutoCrud<Order> {}

The framework hides the Cancel button in the create form and automatically refreshes the list after saving. See Split View for the full behaviour.


Applied to an AutoCrud, FilteredAutoCrud, or Crud class, hides the New, Edit, and Delete buttons. Shorthand for applying @NotCreatable @NotEditable @NotDeletable together.

@UI("/audit-log")
@ReadOnly
public class AuditLog extends AutoCrud<AuditEntry> { ... }

Applied to a field or an EditableView class, makes that field or the entire view non-editable.


Applied to a Crud class. Hides the View button column in the listing — rows are not clickable and do not navigate to the detail screen.

@UI("/products")
@NotNavigable
public class ProductList extends AutoCrud<Product> { ... }

Combine with @ReadOnly for a plain read-only list with no detail view:

@ReadOnly
@NotNavigable
public class AuditLog extends AutoCrud<AuditEntry> { ... }

Applied to a Crud class. Hides the New button in the listing toolbar.

@NotCreatable
public class OrderLines extends AutoCrud<OrderLine> { ... }

Applied to a Crud class. Hides the Edit button in the detail view toolbar.

@NotEditable
public class ArchivedOrders extends AutoCrud<Order> { ... }

Applied to a Crud class. Hides the Delete button in the listing toolbar.

@NotDeletable
public class Invoices extends AutoCrud<Invoice> { ... }

Enables dirty-state tracking on a form. When the user modifies any field, the frontend shows a confirmation dialog if they try to navigate away before saving.

@ConfirmOnNavigationIfDirty
public class MyForm {
String name;
@Toolbar
Object save() {
// persist ...
return UICommand.markAsClean(); // clear dirty flag after save
}
}

Tracking is opt-in: a plain page without this annotation never prompts on exit, even if it has editable fields and a save() action. CRUD create and edit views are the only exception — they activate the behaviour automatically, so the annotation is only needed on standalone forms.

The confirmation covers every way of leaving the form: in-app menu navigation, the browser back/forward buttons, and reloading or closing the tab. Use UICommand.markAsClean() in save actions and UICommand.markAsDirty() to set the flag programmatically.


These are also part of the public DSL and are worth knowing:

  • @Label — set the display label of a field or button (instead of deriving it from the name)
  • @Help — help/hint text on a class, field, or method
  • @LinkTo — navigation icon at the right of a field pointing to a URL or route; the href supports ${state.…} templates interpolated live in the browser, so @LinkTo("/customers/${state.customerId}") follows the value as the user types (optional icon, title, target; for runtime decisions implement LinkSupplier)
  • @Validation — declarative cross-field validation (condition, fieldId, message; repeatable)
  • @Multiline — let a @PlainText value wrap across multiple lines
  • @Text — render a String field as a text block (container picks the HTML element; size = xl/l/m/s/xs scales the font; noMargins = true drops the block margins)
  • @BulletedList — render a List<String> field as a plain bulleted list (<ul>); fluent counterpart BulletedList
  • @SeparatorBefore — paint a full-width divider line (<hr>) above the field; fluent counterpart Separator
  • @Notice — render a String field as a compact inline banner (see Notice); a blank value hides it
  • @UploadableImage — image field with upload/replace/delete, stored as data URI or URL
  • @Signature — signature capture on a String field (drawing canvas → PNG data URI in the value)
  • @PhotoCapture — photo capture on a String field (device camera → JPEG data URI; file-input fallback opens the native camera on phones)
  • @TreeSelect — the field’s dropdown unfolds a TREE of options (children come from the view’s OptionsSupplier); leavesOnly = true restricts selection to leaves
  • @RowAction — per-row contextual actions in listings
  • @MainFilter / @Filterable — mark filter fields for CRUD listings
  • @RangeFilter — render a numeric field’s listing filter as a min–max range (temporal fields are ranges by default); the bounds travel as <field>_from/<field>_to and reach the repository as a FilterCriterion
  • @HomeRoute — designate the default/home route
  • @Fab — floating action button (app level or page level)
  • @AppContext — application-level context selector on the app header (active hotel, company…); options from an enum or a LookupOptionsSupplier, value readable everywhere via httpRequest.appContext(fieldName)
  • @KPI — render a numeric field as a dashboard KPI card
  • @WizardCompletionAction — the method that completes a Wizard (shown on the penultimate step)
  • @WizardProgress — the wizard’s progress style: BAR (default), STEPS (connected step bullets) or RAIL (sticky lateral band: current | total counter over the vertical step list)
  • @AutoSave
  • @Icon
  • @PageTitle
  • @Title
  • @Subtitle
  • @Logo
  • @FavIcon
  • @Widget
  • @Footer
  • @Header
  • @Section (incl. zone for use with @Zones, sticky = true to pin the section card, propertyList = true to render every field as a read-only label-left/value-right row with dividers, and frameless = true to drop the section card and its padding)
  • @Zones / @Zone — side-by-side layout zones (columns of sections); columns stack vertically when they don’t fit (responsive)
  • @Toc — sticky sections index (table of contents) on a long page; see Sticky sections index
  • @Compact — high-density page preset (StyleConstants.COMPACT)
  • @PlainText — render a value as read-only text instead of an input
  • @ColumnWidth — grid column width; a fixed value ("9rem") or "auto" (size to content)
  • @InlineEditing — edit a list/grid field’s rows in place (editable cells) instead of a detail form
  • @Colspan — how many form columns the field spans. Intrinsically wide fields (grids, textareas, rich text, html, markdown) auto-span the full row in a multi-column section, on a row of their own; an explicit @Colspan greater than 1 wins over the automatic rule
  • @HiddenInList
  • @HiddenInView
  • @HiddenInEditor
  • @HiddenInCreate
  • @EditableOnlyWhenCreating
  • @Status
  • @Representation
  • @Details
  • @VerticalLayout
  • @HorizontalLayout
  • @SplitLayout
  • @Accordion
  • @Tabs
  • @Tab — group fields into a tab (value = label; shortcut = keyboard shortcut to select it; open = true makes it the tab selected on first render instead of the first-declared one); see Keyboard shortcuts

The Mateu DSL is annotation-heavy by design.

Annotations define:

  • routing
  • structure
  • behavior
  • relationships
  • rendering
  • security

The result is a declarative application model instead of a separate frontend implementation.