Visibility Annotations
These annotations control whether a field, action, or page is visible, editable, or accessible to the current user and in the current UI context (list, create form, edit form, view).
@Hidden
Section titled “@Hidden”Target: TYPE, FIELD
Hides the annotated element entirely from the rendered UI. When placed on a class, the whole page is hidden. The optional value attribute can hold a condition expression — when the expression evaluates to true, the element is hidden.
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.FIELD})public @interface Hidden { String value() default "";}Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
value | String | "" | Optional condition expression; element is hidden when it evaluates to true. Leave empty to hide unconditionally. |
Example
Section titled “Example”public class OrderForm { String orderId; String status;
@Hidden String internalCode; // never rendered}@HiddenInList
Section titled “@HiddenInList”Target: FIELD
Hides the field in list and grid views only. The field remains visible in create and edit forms and in detail views.
No attributes.
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD})public @interface HiddenInList {}Example
Section titled “Example”record Product( String id, String name, @HiddenInList String description, // shown in the editor, not as a grid column ProductStatus status) {}@HiddenInCreate
Section titled “@HiddenInCreate”Target: FIELD
Hides the field only in the create form. The field is shown when editing an existing record and in read-only views.
No attributes.
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD})public @interface HiddenInCreate {}Example
Section titled “Example”public class OrderForm { @HiddenInCreate String createdAt; // auto-set on first save, not shown when creating}@HiddenInEditor
Section titled “@HiddenInEditor”Target: FIELD
Hides the field in edit mode. The field is still visible during record creation and in read-only (view) mode.
No attributes.
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD})public @interface HiddenInEditor {}Example
Section titled “Example”public class OrderForm { @HiddenInEditor String referenceNumber; // shown in view and create, hidden when editing}@HiddenInView
Section titled “@HiddenInView”Target: FIELD
Hides the field in the read-only detail view. The field is still shown in create and edit forms.
No attributes.
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD})public @interface HiddenInView {}Example
Section titled “Example”public class UserForm { @HiddenInView String passwordHash; // only rendered in the edit form, not in the view}@ReadOnly
Section titled “@ReadOnly”Target: TYPE, FIELD
Makes the field non-editable. The value is displayed but the user cannot change it. When placed on a class, all fields in that page become read-only.
No attributes.
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.FIELD})public @interface ReadOnly {}Example — single field
Section titled “Example — single field”@UI("/profile")@FormLayout(columns = 1)public class ProfileForm { String username;
@ReadOnly String accountId; // displayed but not editable}Example — entire page
Section titled “Example — entire page”@ReadOnlypublic class OrderViewPage { String orderId; String status; String customer;}@Disabled
Section titled “@Disabled”Target: TYPE, FIELD, METHOD
Disables the field or action — it is visible and rendered but not interactive. The optional value attribute holds a condition expression that disables the element only when the expression evaluates to true.
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})public @interface Disabled { String value() default "";}Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
value | String | "" | Optional condition expression; element is disabled when it evaluates to true. Leave empty to disable unconditionally. |
Example
Section titled “Example”public class OrderForm { @Disabled String autoCalculatedTotal; // always disabled
@Button @Disabled("status == 'closed'") void reopen() { } // disabled only when status is closed}@EditableOnlyWhenCreating
Section titled “@EditableOnlyWhenCreating”Target: FIELD
Allows the field to be edited only during new record creation. Once the record is saved, the field becomes read-only in all subsequent edits. This is typically used for primary key or identifier fields that must be set once and never changed.
No attributes.
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD})public @interface EditableOnlyWhenCreating {}Example
Section titled “Example”From the Products demo:
record Product( @NotEmpty @EditableOnlyWhenCreating String id, @NotEmpty String name, @Stereotype(FieldStereotype.textarea) @HiddenInList String description, boolean certified, ProductStatus status, ColumnActionGroup action, @Colspan(2) List<ProductComponent> components) implements Identifiable { }The id field is editable when a new product is created but becomes read-only for all subsequent edits.
@EyesOnly
Section titled “@EyesOnly”Target: FIELD, METHOD, TYPE
Restricts visibility to users who possess at least one of the listed roles, groups, scopes, or permissions. Users who do not match any of the declared constraints do not see the element at all.
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})public @interface EyesOnly { String[] roles() default {}; String[] groups() default {}; String[] scopes() default {}; String[] permissions() default {};}Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
roles | String[] | {} | Required roles — any match grants access |
groups | String[] | {} | Required groups — any match grants access |
scopes | String[] | {} | Required OAuth2 scopes — any match grants access |
permissions | String[] | {} | Required permissions — any match grants access |
Example
Section titled “Example”public class CustomerForm { String name; String email;
@EyesOnly(roles = {"ADMIN", "FINANCE"}) String internalCreditScore;
@Button @EyesOnly(roles = {"ADMIN"}) void deleteCustomer() { }}@Filterable
Section titled “@Filterable”Target: FIELD
Makes the field available as a filter in list views. Mateu renders a filter input for this field in the listing’s filter bar.
No attributes.
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD})public @interface Filterable {}Example
Section titled “Example”record ProductFilters( @Filterable String name, @Filterable ProductStatus status) {}@PrimaryKey
Section titled “@PrimaryKey”Target: FIELD
Marks the field as the entity’s primary key identifier. Mateu uses this to link listing rows to their detail forms.
No attributes.
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD})public @interface PrimaryKey {}Example
Section titled “Example”record CustomerRow( @PrimaryKey String customerId, String name, String email) {}OptionsSupplier
Section titled “OptionsSupplier”Interface — io.mateu.uidl.interfaces.OptionsSupplier
When a ViewModel implements OptionsSupplier, Mateu calls options() to populate the selectable options for any field that renders as a list (select, radio group, checkbox group, etc.). This overrides the options that would otherwise be inferred from the field type (e.g., enum constants).
public interface OptionsSupplier {
default boolean supports(Class<?> fieldType, String fieldName, Class<?> formType) { return true; }
List<Option> options(String fieldName, HttpRequest httpRequest);}Override supports() to limit which fields the supplier applies to. The default implementation returns true for all fields.
Example
Section titled “Example”@UI("/products/{id}")public class ProductForm implements OptionsSupplier {
public String category; public String subcategory;
@Override public boolean supports(Class<?> fieldType, String fieldName, Class<?> formType) { return "subcategory".equals(fieldName); }
@Override public List<Option> options(String fieldName, HttpRequest httpRequest) { return switch (category) { case "electronics" -> List.of( new Option("phones", "Phones"), new Option("laptops", "Laptops")); case "clothing" -> List.of( new Option("shirts", "Shirts"), new Option("shoes", "Shoes")); default -> List.of(); }; }}StereotypeSupplier
Section titled “StereotypeSupplier”Interface — io.mateu.uidl.interfaces.StereotypeSupplier
When a ViewModel implements StereotypeSupplier, Mateu calls stereotype() on the server for every field before building the UIDL. A non-null return value overrides any @Stereotype annotation or the inferred stereotype for the field; returning null falls back to the annotation or inference.
public interface StereotypeSupplier { FieldStereotype stereotype(String memberName, HttpRequest httpRequest);}Example
Section titled “Example”@UI("/products/{id}")public class ProductForm implements StereotypeSupplier {
public String category; public String description;
@Override public FieldStereotype stereotype(String memberName, HttpRequest httpRequest) { return switch (memberName) { case "description" -> "long".equals(category) ? FieldStereotype.textarea : FieldStereotype.regular; default -> null; }; }}Comparison with @Stereotype
Section titled “Comparison with @Stereotype”@Stereotype | StereotypeSupplier | |
|---|---|---|
| Evaluated | Server (static) | Server only |
| Condition | Fixed stereotype | Any Java logic |
| Scope | Per field | Per ViewModel, all fields |
ColspanSupplier
Section titled “ColspanSupplier”Interface — io.mateu.uidl.interfaces.ColspanSupplier
When a ViewModel implements ColspanSupplier, Mateu calls colspan() on the server for every field before building the UIDL. A positive return value overrides any @Colspan annotation on the field; returning 0 or negative falls back to the annotation or the default of 1.
public interface ColspanSupplier { int colspan(String memberName, HttpRequest httpRequest);}Example
Section titled “Example”@UI("/orders/{id}")public class OrderForm implements ColspanSupplier {
public String type; public String notes; public String address;
@Override public int colspan(String memberName, HttpRequest httpRequest) { return switch (memberName) { case "notes" -> 2; // always full-width case "address" -> "international".equals(type) ? 2 : 1; default -> 0; // use annotation or default }; }}Comparison with @Colspan
Section titled “Comparison with @Colspan”@Colspan | ColspanSupplier | |
|---|---|---|
| Evaluated | Server (static) | Server only |
| Condition | Fixed number of columns | Any Java logic |
| Scope | Per field | Per ViewModel, all fields |
LabelSupplier
Section titled “LabelSupplier”Interface — io.mateu.uidl.interfaces.LabelSupplier
When a ViewModel implements LabelSupplier, Mateu calls label() on the server for every field before building the UIDL. A non-empty return value overrides any @Label annotation on the field; returning null or an empty string falls back to the annotation or to the humanized field name.
public interface LabelSupplier { String label(String memberName, HttpRequest httpRequest);}Example
Section titled “Example”@UI("/invoices/{id}")public class InvoiceForm implements LabelSupplier {
public String type; public String counterpart;
@Override public String label(String memberName, HttpRequest httpRequest) { return switch (memberName) { case "counterpart" -> "business".equals(type) ? "Company name" : "Full name"; default -> null; }; }}Comparison with @Label
Section titled “Comparison with @Label”@Label | LabelSupplier | |
|---|---|---|
| Evaluated | Server (static) | Server only |
| Condition | Fixed text | Any Java logic |
| Scope | Per field or method | Per ViewModel, all fields |
Note:
LabelSuppliercontrols form field labels. For resolving the display text of a lookup value (given an id), useLookupLabelSupplierinstead.
DescriptionSupplier
Section titled “DescriptionSupplier”Interface — io.mateu.uidl.interfaces.DescriptionSupplier
When a ViewModel implements DescriptionSupplier, Mateu calls description() on the server for every field before building the UIDL. A non-empty return value overrides any @Help annotation on the field; returning null or an empty string falls back to the annotation.
public interface DescriptionSupplier { String description(String memberName, HttpRequest httpRequest);}Example
Section titled “Example”@UI("/products/{id}")public class ProductForm implements DescriptionSupplier {
public String category; public double price;
@Override public String description(String memberName, HttpRequest httpRequest) { return switch (memberName) { case "price" -> "business".equals(category) ? "Net price excluding VAT" : "Consumer price including VAT"; default -> null; }; }}Comparison with @Help
Section titled “Comparison with @Help”@Help | DescriptionSupplier | |
|---|---|---|
| Evaluated | Server (static) | Server only |
| Condition | Fixed help text | Any Java logic |
| Scope | Per field | Per ViewModel, all fields |
StyleSupplier
Section titled “StyleSupplier”Interface — io.mateu.uidl.interfaces.StyleSupplier
When a ViewModel implements StyleSupplier, Mateu calls style() on the server for every field before building the UIDL. A non-empty return value overrides any @Style annotation on the field; returning null or an empty string falls back to the annotation.
public interface StyleSupplier { String style(String memberName, HttpRequest httpRequest);}Example
Section titled “Example”@UI("/orders/{id}")public class OrderForm implements StyleSupplier {
public String status; public double total;
@Override public String style(String memberName, HttpRequest httpRequest) { return switch (memberName) { case "total" -> total < 0 ? "color: red;" : ""; default -> null; }; }}Comparison with @Style
Section titled “Comparison with @Style”@Style | StyleSupplier | |
|---|---|---|
| Evaluated | Server (static) | Server only |
| Condition | Fixed CSS string | Any Java logic |
| Scope | Per field | Per ViewModel, all fields |
RequiredSupplier
Section titled “RequiredSupplier”Interface — io.mateu.uidl.interfaces.RequiredSupplier
When a ViewModel implements RequiredSupplier, Mateu calls isRequired() on the server for every field before building the UIDL. Fields for which isRequired() returns true are marked as required — equivalent to annotating the field with @NotNull or @NotEmpty.
Use this when the required state depends on runtime conditions that cannot be expressed statically.
public interface RequiredSupplier { boolean isRequired(String memberName, HttpRequest httpRequest);}Example
Section titled “Example”@UI("/orders/{id}")public class OrderForm implements RequiredSupplier {
public String type; public String vatNumber; public String personalId;
@Override public boolean isRequired(String memberName, HttpRequest httpRequest) { return switch (memberName) { case "vatNumber" -> "business".equals(type); case "personalId" -> "individual".equals(type); default -> false; }; }}Comparison with @NotNull / @NotEmpty
Section titled “Comparison with @NotNull / @NotEmpty”@NotNull / @NotEmpty | RequiredSupplier | |
|---|---|---|
| Evaluated | Server (static) | Server only |
| Condition | Always required | Any Java logic |
| Scope | Per field | Per ViewModel, all fields |
ReadOnlySupplier
Section titled “ReadOnlySupplier”Interface — io.mateu.uidl.interfaces.ReadOnlySupplier
When a ViewModel implements ReadOnlySupplier, Mateu calls isReadOnly() on the server for every field before building the UIDL. Fields for which isReadOnly() returns true are rendered as read-only — displayed but not editable — exactly as if they were annotated with @ReadOnly.
Use this when the read-only state depends on runtime state that cannot be expressed as a static annotation.
public interface ReadOnlySupplier { boolean isReadOnly(String memberName, HttpRequest httpRequest);}The memberName parameter is the Java field name of the field being evaluated.
Example
Section titled “Example”@UI("/orders/{id}")public class OrderForm implements ReadOnlySupplier {
public String status; public String customerId; public double total;
@Override public boolean isReadOnly(String memberName, HttpRequest httpRequest) { return switch (memberName) { case "customerId" -> !"draft".equals(status); case "total" -> true; // always computed, never editable default -> false; }; }}Comparison with @ReadOnly
Section titled “Comparison with @ReadOnly”@ReadOnly | ReadOnlySupplier | |
|---|---|---|
| Evaluated | Server (static) | Server only |
| Condition | Always read-only | Any Java logic |
| Scope | Per field or class | Per ViewModel, all fields |
DisabledSupplier
Section titled “DisabledSupplier”Interface — io.mateu.uidl.interfaces.DisabledSupplier
When a ViewModel implements DisabledSupplier, Mateu calls isDisabled() on the server for every field, button, and toolbar item before building the UIDL. Members for which isDisabled() returns true are rendered as disabled — visible but non-interactive — exactly as if they were annotated with @Disabled.
Use this when the disabled state depends on runtime state that cannot be expressed as a static annotation or a client-side expression.
public interface DisabledSupplier { boolean isDisabled(String memberName, HttpRequest httpRequest);}The memberName parameter is the Java field name or method name of the member being evaluated.
Example
Section titled “Example”@UI("/orders/{id}")public class OrderForm implements DisabledSupplier {
public String status; public String notes;
@Button public void submit() { ... }
@Button public void cancel() { ... }
@Override public boolean isDisabled(String memberName, HttpRequest httpRequest) { return switch (memberName) { case "notes" -> !"draft".equals(status); case "submit" -> !"draft".equals(status); case "cancel" -> "cancelled".equals(status) || "completed".equals(status); default -> false; }; }}Comparison with @Disabled
Section titled “Comparison with @Disabled”@Disabled | DisabledSupplier | |
|---|---|---|
| Evaluated | Client (always or via expression) | Server only |
| Condition | Static or client expression | Any Java logic |
| Scope | Per field or method | Per ViewModel, all members |
VisibilitySupplier
Section titled “VisibilitySupplier”Interface — io.mateu.uidl.interfaces.VisibilitySupplier
When a ViewModel implements VisibilitySupplier, Mateu calls isHidden() on the server for every field, listing column, filter field, button, and toolbar item before building the UIDL. Members for which isHidden() returns true are excluded from the response entirely, exactly as if they were annotated with @Hidden.
Use this when visibility depends on runtime state — fetched data, user context, or business rules — that cannot be expressed as a static annotation or a client-side expression.
public interface VisibilitySupplier { boolean isHidden(String memberName, HttpRequest httpRequest);}The memberName parameter is the Java field name or method name of the member being evaluated.
Example
Section titled “Example”@UI("/orders/{id}")public class OrderForm implements VisibilitySupplier {
public String status; public String internalNote; public double discount;
@Button public void approve() { ... }
@Override public boolean isHidden(String memberName, HttpRequest httpRequest) { return switch (memberName) { case "internalNote" -> !httpRequest.isUserInRole("ADMIN"); case "discount" -> !"draft".equals(status); case "approve" -> !"pending".equals(status); default -> false; }; }}In this example:
internalNoteis only sent to admins.discountis only sent when the order is in draft.- The
approvebutton is only sent when the order is pending.
Comparison with @Hidden
Section titled “Comparison with @Hidden”@Hidden | VisibilitySupplier | |
|---|---|---|
| Evaluated | Server (annotation) or client (expression) | Server only |
| Condition | Static or client expression | Any Java logic |
| Scope | Per field or class | Per ViewModel, all members |
Combining visibility annotations
Section titled “Combining visibility annotations”Annotations compose freely. A common pattern is using @HiddenInList together with @HiddenInCreate for a computed or derived field that only makes sense in the edit and view forms:
record Invoice( @EditableOnlyWhenCreating String invoiceNumber,
String customerId, double subtotal,
// Computed server-side; hidden in the grid and not shown on create @HiddenInList @HiddenInCreate double vatAmount,
// Only administrators should see the internal margin @EyesOnly(roles = {"ADMIN"}) double margin) {}In this example:
invoiceNumberis set once at creation and locked thereafter.vatAmountis computed after the first save, so it is suppressed from the grid column list and from the create form.marginis only visible to users with theADMINrole.