Skip to content

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).


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 "";
}
AttributeTypeDefaultDescription
valueString""Optional condition expression; element is hidden when it evaluates to true. Leave empty to hide unconditionally.
public class OrderForm {
String orderId;
String status;
@Hidden
String internalCode; // never rendered
}

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 {}
record Product(
String id,
String name,
@HiddenInList String description, // shown in the editor, not as a grid column
ProductStatus status
) {}

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 {}
public class OrderForm {
@HiddenInCreate
String createdAt; // auto-set on first save, not shown when creating
}

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 {}
public class OrderForm {
@HiddenInEditor
String referenceNumber; // shown in view and create, hidden when editing
}

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 {}
public class UserForm {
@HiddenInView
String passwordHash; // only rendered in the edit form, not in the view
}

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 {}
@UI("/profile")
@FormLayout(columns = 1)
public class ProfileForm {
String username;
@ReadOnly
String accountId; // displayed but not editable
}
@ReadOnly
public class OrderViewPage {
String orderId;
String status;
String customer;
}

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 "";
}
AttributeTypeDefaultDescription
valueString""Optional condition expression; element is disabled when it evaluates to true. Leave empty to disable unconditionally.
public class OrderForm {
@Disabled
String autoCalculatedTotal; // always disabled
@Button
@Disabled("status == 'closed'")
void reopen() { } // disabled only when status is closed
}

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 {}

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.


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 {};
}
AttributeTypeDefaultDescription
rolesString[]{}Required roles — any match grants access
groupsString[]{}Required groups — any match grants access
scopesString[]{}Required OAuth2 scopes — any match grants access
permissionsString[]{}Required permissions — any match grants access
public class CustomerForm {
String name;
String email;
@EyesOnly(roles = {"ADMIN", "FINANCE"})
String internalCreditScore;
@Button
@EyesOnly(roles = {"ADMIN"})
void deleteCustomer() { }
}

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 {}
record ProductFilters(
@Filterable String name,
@Filterable ProductStatus status
) {}

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 {}
record CustomerRow(
@PrimaryKey String customerId,
String name,
String email
) {}

Interfaceio.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.

@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();
};
}
}

Interfaceio.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);
}
@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;
};
}
}
@StereotypeStereotypeSupplier
EvaluatedServer (static)Server only
ConditionFixed stereotypeAny Java logic
ScopePer fieldPer ViewModel, all fields

Interfaceio.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);
}
@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
};
}
}
@ColspanColspanSupplier
EvaluatedServer (static)Server only
ConditionFixed number of columnsAny Java logic
ScopePer fieldPer ViewModel, all fields

Interfaceio.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);
}
@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;
};
}
}
@LabelLabelSupplier
EvaluatedServer (static)Server only
ConditionFixed textAny Java logic
ScopePer field or methodPer ViewModel, all fields

Note: LabelSupplier controls form field labels. For resolving the display text of a lookup value (given an id), use LookupLabelSupplier instead.


Interfaceio.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);
}
@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;
};
}
}
@HelpDescriptionSupplier
EvaluatedServer (static)Server only
ConditionFixed help textAny Java logic
ScopePer fieldPer ViewModel, all fields

Interfaceio.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);
}
@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;
};
}
}
@StyleStyleSupplier
EvaluatedServer (static)Server only
ConditionFixed CSS stringAny Java logic
ScopePer fieldPer ViewModel, all fields

Interfaceio.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);
}
@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;
};
}
}
@NotNull / @NotEmptyRequiredSupplier
EvaluatedServer (static)Server only
ConditionAlways requiredAny Java logic
ScopePer fieldPer ViewModel, all fields

Interfaceio.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.

@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;
};
}
}
@ReadOnlyReadOnlySupplier
EvaluatedServer (static)Server only
ConditionAlways read-onlyAny Java logic
ScopePer field or classPer ViewModel, all fields

Interfaceio.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.

@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;
};
}
}
@DisabledDisabledSupplier
EvaluatedClient (always or via expression)Server only
ConditionStatic or client expressionAny Java logic
ScopePer field or methodPer ViewModel, all members

Interfaceio.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.

@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:

  • internalNote is only sent to admins.
  • discount is only sent when the order is in draft.
  • The approve button is only sent when the order is pending.
@HiddenVisibilitySupplier
EvaluatedServer (annotation) or client (expression)Server only
ConditionStatic or client expressionAny Java logic
ScopePer field or classPer ViewModel, all members

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:

  • invoiceNumber is set once at creation and locked thereafter.
  • vatAmount is computed after the first save, so it is suppressed from the grid column list and from the create form.
  • margin is only visible to users with the ADMIN role.