Arne Vandamme, Steven Gentens


The EntityModule is responsible for automatically generating an administration UI of entities built. It provides infrastructure to define an entity model and to generate default web pages based on that model. By default all Spring Data repositories are introspected and corresponding entity models are built for every repository.

What’s new in this version?


  • the module dependencies for EntityModule have been optimized for re-use

    • as a result EntityModule no longer transitively pulls in BootstrapUiModule or AdminWebModule

    • when used without BootstrapUiModule, no default ViewElement rendering infrastructure will be available

    • when used with BootstrapUiModule but without AdminWebModule, the default views for an entity will never get created and view support (EntityViewFactory) will be disabled

  • added support for LocalDate, LocalTime and LocalDateTime to be rendered using DateTimeFormElement

  • it is now possible to configure default view element modes (eg. control or readonly rendering) on an EntityConfiguration

    • these will be used in all cases where no specific configuration has been configured on property level

  • configuration & view builders support AttributeRegistrar for registering or removing attributes

    • using AttributeRegistrar is useful if you want to use the owner of the attribute collection (eg. the EntityConfiguration)

    • common default registrars can be found in the EntityAttributeRegistrars utility class

  • entity views can now have a collection of configuration attributes

    • attributes can be used to influence or extend default behaviour, new attributes are available for permission checking and admin menu rendering

    • during view rendering attributes are accessible (and can be modified) using EntityViewRequest.getConfigurationAttributes()

  • improvements to view configuration

    • EntityViewFactoryAttributes.ADMIN_MENU attribute can be used to specify if a view should have an admin menu item added

    • EntityViewFactoryAttributes.ACCESS_VALIDATOR attribute can be used to determine how access to the view should be validated

  • added an ExtensionViewProcessorAdapter base class for easily creating a view for a custom extension class (see how-to)

  • added EntityViewCustomizers utility class providing some helpers for customizing EntityViewFactoryBuilder in a chainable fashion

  • EntityModule no longer creates its own Validator instance, the registerForMvc related settings have been removed

    • the validator used by EntityModule is the default MVC validator

  • it’s now possible to define a different message code prefix for module entities using properties

  • you can now force the required status of a control by setting the EntityAttributes.REQUIRED_PROPERTY attribute to true or false on an EntityPropertyDescriptor

  • message codes for form groups and fieldsets have been extended, apart from [description], there is now also built-in support for [help] and [tooltip]

    • this constitutes a minor breaking change in that [description] content is now always rendered above the control of a form group. Previously this could be different depending on the type of control inside the form group.

    • see the section configuring form controls text for a full explanation of the new message codes

  • the behaviour of when controls are prefixed with entity. has been changed

    • when using EntityViewCommand all property controls of the base entity will should be prefixed with entity. in order to map on the EntityViewCommand.entity values

    • previously this was done always when an EntityViewCommand was found on the ViewElementBuilderContext

    • in the new version this is only done if there is also an attribute EntityPropertyControlNamePostProcessor.PREFIX_CONTROL_NAMES explicitly set to true on the builder context

      • the latter is done automatically by the PropertyRenderingViewProcessor when building the initial controls

    • though not intentionally breaking, this change can have side effects with controls no longer being prefixed, developers are encouraged to test the custom forms they have

  • new components for linking to entity views have been introduced

    • the old EntityLinkBuilder interface and attributes are deprecated, but should still work as before

    • see the chapter on linking to entity views for an overview of the new components


  • added message codes for admin menu items

    • of the entity management section

    • of a group of entities (defaults to the module name)

    • of a single entity type

  • EntityQuery Language allows specifying an order by clause or a Sort specifier

  • OptionIterableBuilder can return a sorted specification by implementing the isSorted() method

    • if the OptionGenerator has no explicit sorting parameter set, it now only sorts if the configured OptionIterableBuilder is not sorted

    • if you specify option values using an EQL statement, the sort specifier of your EntityQuery will be taken into account

  • EntityQuery filtering now supports a basic and advanced mode to support the use of configured property filters and the use of eql statements


  • improve the ability to customize page titles and layouts

    • all entity views now set a page \(sub\) title if a matching message code returns a non-empty string

      • there is a default title for all views except the list view

    • list views now also publish an EntityPageStructureRenderedEvent

  • select option controls now support SelectFormElementConfiguration to render more advanced bootstrap-select controls

  • added ILIKE operator to the EntityQuery Language for case insensitive matching on String columns

    • an EntityQueryConditionTranslator attribute can be registered on entity properties to ensure regular equal and like lookups are always converted to the case insensitive equivalent


This release has a lot of breaking changes compared with the previous release. The code has been heavily rewritten and optimized. The public API modified accordingly with a focus on simplification and future extensibility.

  • requires Across 2.0.0+

  • massive overhaul of the EntitiesConfigurationBuilder system - removed the and() concatenating of builder calls

  • massive overhaul of EntityViewFactory, EntityViewProcessor and the default administration controllers

    • nested builder consumers are used instead - this greatly simplified the class hierarchy involved

    • externalized the entire ViewElement infrastructure to BootstrapUiModule

    • if BootstrapUiModule is not present, default views will not be created

  • compatibility update with Spring 4.2 which replaces CrudInvoker with RepositoryInvoker from spring-data-commons.

  • principal names on Auditable entities are now pretty printed using the SecurityPrincipalLabelResolverStrategy from the SpringSecurityModule

  • EntityModule now supports deleting of entities

  • the EntityModel of an EntityConfiguration can now be customized using the EntityConfigurer builders

  • extension of the EntityQuery infrastructure

    • addition of the EntityQuery Language \(EQL\) providing SQL-like syntax for building an EntityQuery

    • provide a default EQL-based filter for list views

  • addition of the entity browser in the Developer tools section of AdminWebModule

    • allows seeing all registered entities along with their attributes, properties, views and associations

    • the entity browser is only activate if development mode is active

  • streamlined the message code hierarchy for view rendering, see appendix for details

  • a list view can now have a default predicate assigned using an EQL statement

    • this can be used to ensure a list result always has a default filter applied

  • default entity views support transactions, allowing multiple processors to modify data in a single transaction

    • transactions are enabled by default for state modifying HTTP methods of all form views \(create, update, delete and custom form views\)

  • option controls \(select, multi-checkbox\) can be easily customized through a number of attributes

    • making it easier to specify the option values that can be selected

General information



Module dependencies

Module Type Description



Enables generating and customizing forms (views) for managing the registered entities.



Activates support for default Bootstrap based ViewElement creation and rendering.

Module settings

EntityModule supports the following configuration properties:

# Customize the message code prefix that should be used for entity messages
# for all entities from that module
entityModule.message-codes[MODULE_NAME] = PREFIX

How EntityModule works

1. Entity views

EntityModule has the ability to generate automatic views for entities. When AdminWebModule is present, automatic CRUD pages for entities will be generated using these views.

A view is an abstraction of web controller that allows you to take advantage of the EntityModule configuration system. Usually views are a rendering of one or more entities of a particular entity type.

Both EntityConfiguration and EntityAssociation can have their own views defined. They do so by implementing the EntityViewRegistry interface. Every view has a unique name within an EntityViewRegistry.

1.1. Default views

By default CRUD views will be created for every EntityConfiguration with a CrudRepository or an EntityQueryExecutor registered. Likewise CRUD views will be created for every EntityAssociation with an AssociatedEntityQueryExecutor attribute.

Query executor attributes should be registered automatically in most cases. So you usually don’t need to worry about those. See the chapter on EntityQueryExecutor for more information.

The following default views will be created for an entity type:

View name View type Description

listView (EntityView.LIST_VIEW_NAME)

list view

Shows the list of entities.

createView (EntityView.CREATE_VIEW_NAME)

form view

Renders and executes the form for creating a new entity.

updateView (EntityView.UPDATE_VIEW_NAME)

form view

Renders and executes the form for updating an entity.

deleteView (EntityView.DELETE_VIEW_NAME)

form view

Shows the confirmation page and performs the delete if allowed.

The default views all render markup using the ViewElement infrastructure and the implementations provided by the BootstrapUiModule.

1.1.1. How entity views work

An EntityView is created by an EntityViewFactory based on an EntityViewRequest. A controller is still responsible for executing (usually rendering) the actual EntityView.

The GenericEntityViewController is responsible for loading and rendering all default views in the AdminWebModule UI.

The path of the web request to GenericEntityViewController will determine:

  • the name of the view that should be rendered

  • which EntityViewRegistry will be used for loading the view (EntityConfiguration or EntityAssociation)

You can force the view being rendered by simply providing the view name as a request parameter with key view.

The controller creates an EntityViewRequest, retrieves the EntityViewFactory and builds the corresponding EntityViewContext and EntityViewCommand.

1.1.2. EntityViewRequest

Wraps together all the information of a specific entity view request:

  • the web request that is creating the view

  • the contextual information of the entity being viewed (represented by the EntityViewContext)

  • the command that should be executed on the EntityViewFactory (represented by the EntityViewCommand)

  • the name of the view being requested and the EntityViewFactory being used

  • the PageContentStructure of the page being rendered (if applicable)

Please refer to the EntityViewRequest javadoc for a description of all available properties.

1.1.3. EntityViewContext

The EntityViewContext contains contextual information and provides access to components that should be used for building a view. Contextual information is for example the entity type, specific entity or entity association being viewed. Components are things like the EntityLinkBuilder, EntityMessages or AllowableActions

Please refer to the EntityViewContext javadoc for a description of all available properties.

EntityViewContext is available as a request-scoped bean but only in case of a request passing through the GenericEntityViewController will the data be loaded. Loading the EntityViewContext attributes manually can be done by using the EntityViewContextLoader bean. You should use the ConfigurableEntityViewContext interface to modify a view context.

1.1.4. EntityViewCommand

The EntityViewCommand represents the command that should be executed on the EntityViewFactory to create the corresponding EntityView. Most often the command represents the form of a view where the request parameters are bound to.

An EntityViewCommand has 2 attributes: the (optional) entity DTO being bound and a map of named extensions. Customizing view processing can be done by registering additional extensions that will be rendered and validated upon binding.

The EntityViewCommand will be validated for every state modifying request: web requests using POST, PUT, PATCH or DELETE.

Please refer to the EntityViewCommand javadoc for a description of all available properties.

1.1.5. EntityViewFactory

Every view is built by an EntityViewFactory. An EntityViewRegistry (like EntityConfiguration or EntiyAssociation) is nothing but a map of EntityViewFactory by view name.

An EntityViewFactory is a stateless controller with 4 controller methods that are expected to be called in a specific order. Please refer to the javadoc for an overview of the different controller methods and their purpose.

DispatchingEntityViewFactory and EntityViewProcessor

EntityModule has only a single implementation of EntityViewFactory in the form of the DefaultEntityViewFactory. The DefaultEntityViewFactory:

  • supports ViewElement based rendering model - heavily used by EntityModule for building the HTML markup

  • dispatches its controller methods in a more fine-grained manner to a list of registered and ordered EntityViewProcessor instances.

The default builders when configuring views only register EntityViewProcessor beans on the factory. When an EntityViewFactory controller method is being called, it will be translated into one or more processor methods and those method calls will be dispatched to all processors in order.

Several adapter EntityViewProcessor classes are available:

  • use EntityViewProcessorAdapter for most use cases, it provides several adapter methods that also allow you to hook into the ViewElement generation lifecycle

  • use SimpleEntityViewProcessorAdapter if you do not need to hook into the ViewElement generation lifecycle

  • use ExtensionViewProcessorAdapter if you want to create a custom view/form that represents a so-called extension object

    • the view is part of the entity configuration but often does not manage properties of the actual entity type

The DefaultEntityViewFactory will not execute any of the rendering related methods if any of the previous methods has marked the EntityView as being a redirect.

Please refer to the EntityViewProcessor, EntityViewProcessorAdapter and SimpleEntityViewProcessorAdapter for more details on the available processor methods. The appendix also provides a list of all available general purpose processors.

Transaction support

The DefaultEntityViewFactory uses a TransactionalEntityViewProcessorRegistry and enables transactions on all state modifying HTTP methods: POST, PUT, PATCH or DELETE. If a transaction manager bean name is available on the EntityConfiguration, transactions will be enabled by default for all form views: create, update, delete and custom form views. This means that all calls in state modifying doControl() methods of all EntityViewProcessor instances will happen in a single transaction.

The transaction manager bean name is registered as an attribute EntityAttributes.TRANSACTION_MANAGER_NAME and is detected automatically for every Spring Data repository based entity.

Manually enabling transactions on a view

You can enable transactions manually on the EntityViewFactoryBuilder by specifying either the PlatformTransactionManager to use, the name of the transaction manager bean or a TransactionTemplate if you need more fine-grained control.

1.1.6. Model attributes

The GenericEntityViewController exposes the following model attributes to the Spring MVC view:

Attribute name Value







1.1.7. Default view types

EntityModule supports 3 view types by default.

When defining a new view (see the next section) it will be one of these types. The view type determines the base template that will be used to setup the EntityViewFactory.

The following view types are defined:

View type Template name Description

list view

listView (EntityView.LIST_VIEW_NAME)

Base configuration for rendering a list of entities.

form view

updateView (EntityView.UPDATE_VIEW_NAME)

Base configuration for rendering a form for a single entity.

generic view

genericView (EntityView.GENERIC_VIEW_NAME)

Barebone configuration for visualizing a single entity.

The template name can be used to replace the initializer for the EntityViewFactoryBuilder. See the chapter on the EntityViewFactoryBuilderInitializer.

See also the next chapters for more information on list view, form view and generic view.

1.1.8. Configuring views

Existing views can be modified or new ones registered using an EntityViewFactoryBuilder or EntityListViewFactoryBuilder. You usually don’t create these manually but get a builder for the corresponding view from the configuration or association builder.

The builders provide common properties that will configure one or more EntityViewProcessor instances on the view factory. They also allow you to modify the processor collection directly by adding or removing processors, or by post-processing the entire EntityViewProcessorRegistry.

Example adding an EntityViewProcessor to the default list view
configuration.withType( MyEntity.class )
             .listView( lvb -> lvb.viewProcessor( myProcessor ) );

The following chapters provide some more details on how to configure the default view types.

1.2. List view

default settings of a list view:

  • user must have read allowable action

  • renders values as LIST_VALUE

  • shows all readable properties

  • adds update/delete buttons for every item if the user as update and delete action respectively

  • supports paging and sorting

  • allows configuring sortable properties and the default sort

  • includes a form at the top that can be used for adding filters

  • a create button for the entity if the user has create action allowed

  • supports global feedback messages set with the EntityViewPageHelper

default processors

1.2.1. Adding a list view


1.2.2. Customizing a list view


Custom sorting

Customizing the sort behaviour of a specific property can be done by setting a Sort.Order.class attribute on the EntityPropertyDescriptor. You can use this to sort on a different property instead, or to specify behaviour for null handling and case sensitivity.

1.2.3. Filtering a list view

By default a list view is not filtered. If your entity has an EntityQueryExecutor available you can easily activate a simple yet powerful EQL-based filter. Activating EntityQuery based filtering can be done through the builders:

// Activate the EQL-based filter on every entity that has an EntityQueryExecutor
configuration.matching( c -> c.hasAttribute( EntityQueryExecutor.class ) )
             .listView( lvb -> lvb.entityQueryFilter( true ) );

See this chapter for information on how to configure a custom filter for a list view.

1.2.4. Adding a custom filter to a list view

A filter on a list view usually consists of 2 parts:

  • an EntityViewProcessor that provides the filtering options on the list view

  • an EntityViewProcessor that fetches the items using the filtering options

    • a custom form is usually added to the EntityViewCommand.addExtension() for both postback and optional validation

Both parts can easily be combined in a single EntityViewProcessor.

Custom filter example

The following code illustrates adding a simple filter to a view. The filter uses a separate repository method to lookup entities by name. The filter options are added as a form on top of the list view, the form in this case rendered via a custom Thymeleaf template.

Implementation of a single class that holds all filter logic
private static class GroupFilteringProcessor extends EntityViewProcessorAdapter
        private GroupRepository groupRepository;

    public void initializeCommandObject( EntityViewRequest entityViewRequest,
                                         EntityViewCommand command,
                                         WebDataBinder dataBinder ) {
        command.addExtension( "filter", "" );

    protected void doControl( EntityViewRequest entityViewRequest,
                              EntityView entityView,
                              EntityViewCommand command,
                              BindingResult bindingResult,
                              HttpMethod httpMethod ) {
        String filter = command.getExtension( "filter", String.class );
        Pageable pageable = command.getExtension( PageableExtensionViewProcessor.DEFAULT_EXTENSION_NAME, Pageable.class );

        if ( !StringUtils.isBlank( filter ) ) {
            entityView.addAttribute( "items", groupRepository.findByNameContaining( filter, pageable ) );
        else {
            entityView.addAttribute( "items", groupRepository.findAll( pageable ) );

    protected void postRender( EntityViewRequest entityViewRequest,
                               EntityView entityView,
                               ContainerViewElement container,
                               ViewElementBuilderContext builderContext ) {
        Optional<ContainerViewElement> header = find( container, "entityListForm-header", ContainerViewElement.class );
                h -> {
                    Optional<NodeViewElement> actions
                            = find( h, "entityListForm-header-actions", NodeViewElement.class );
                    actions.ifPresent( a -> a.addCssClass( "pull-right" ) );

                    h.addChild( new TemplateViewElement( "th/entityModuleTest/filters :: filterForm" ) );
Custom Thymeleaf template that builds the form
<fragments xmlns:th="">
    <div class="list-header form form-inline" th:fragment="filterForm">
        <div class="form-group">
            <label for="group-name-filter">Filter by name:</label>
            <input id="group-name-filter" name="extensions[filter]" th:value="${entityViewCommand.extensions['filter']}" type="text" class="form-control" />
        <input type="submit" class="btn btn-default" value="Apply filter" />
Registration of the custom filter on the list view
entities.withType( Group.class )
        .listView( lvb -> lvb
            .entityQueryFilter( false )           // optional - disable the previously activated entity query filter
            .filter( groupFilteringProcessor() )  // register the custom filter

1.2.5. List summary view

It is possible to activate a detail view inline in a list view. If the EntityConfiguration or EntityAssociation has a view named listSummaryView a summary pane will automatically become available when clicking on the item row in the table. The summary pane is called using AJAX and only the content fragment of the page will be rendered.

// Activate a summary view in the main user results table using a custom Thymeleaf template
configuration.withType( User.class )
             .view( EntityView.SUMMARY_VIEW_NAME, vb -> vb.template( "th/myModule/userSummary" ) );

1.3. Form view

1.3.1. create and update view

default settings of a form view:

  • user must have update allowable action

  • renders values as LIST_VALUE

  • shows all readable properties

  • adds update/delete buttons for every item if the user as update and delete action respectively

  • supports paging and sorting

  • allows configuring sortable properties and the default sort

  • includes a form at the top that can be used for adding filters

  • a create button for the entity if the user has create action allowed

  • supports global feedback messages set with the EntityViewPageHelper

default processors

1.4. Delete view

default settings

default processors

A delete action will be available for all entities where AllowableAction.DELETE is present, this is the default unless more explicit permissions are configured. A delete will always redirect to a confirmation page by default. Because the possibility to delete an entity often depends on other factors (usually associations), the default EntityDeleteViewFactory publishes an event that allows customizing said confirmation page.

By catching the BuildEntityDeleteViewEvent your code can:

  • suppress the ability to delete (by hiding the delete button)

  • add associations to the form

  • add custom feedback messages to the form (and optionally remove the associations block)

This should be sufficient for most use cases without having to revert to custom EntityViewProcessor implementations. Of course the latter would work as well.

Entity associations

The initial BuildEntityDeleteViewEvent is configured based on the EntityAssociation list of the entity. If associated items are detected, they influence the form settings depending on the parentDeleteMode property of the EntityAssociation:

  • ParentDeleteMode.IGNORE: item information is not printed nor influences the ability to delete

  • ParentDeleteMode.WARN: item information is printed on the form but does not influence the ability to delete

  • ParentDeleteMode.SUPPRESS: item information is printed on the form and disables the ability to delete, this is the default setting

The event is published after the initial association information has been set.

Performing the delete

The EntityModule simply calls the delete method of the EntityModel, usually a direct call to a repository delete(). You will have to take care yourself of complex delete scenarios - like deleting the associations - by either modifying the EntityModel or using another mechanism like the EntityInterceptor.

1.4.1. creating an additional form view

1.4.2. Configuring form controls text

A form view usually renders properties in either ViewElementMode.FORM_WRITE or ViewElementMode.FORM_READ, depending on the fact if a property can be edited or not. By default, a property like this would be rendered as a form group (FormGroupElement that is usually a combination of the label and the control for the property) or a fieldset (FieldsetFormElement).

In FORM_READ (readonly) mode, the default form renders only the label of a property. You can customize the label value by setting the corresponding message code, for example: of the user.

In FORM_WRITE mode several other message codes will be resolved as well, and if they return values, additional content will be shown on the form.

Description text

A description provides additional context for the property being shown. It is rendered above the control of a form group, or above the content of a fieldset.[description]=The username must be unique.
Help text

Help text is rendered below the control of a form group, or below the content of a fieldset. It usually provides a (less important) hint for updating the value.[help]=Try to pick something you will remember.
Tooltip text

Tooltip text is added as a separate icon (question mark) that will only show the actual tooltip when you hover over it with the mouse cursor. Tooltips are often used as an alternative for help text. The difference is that help text is always visible, whereas to see the tooltip a used will need to take an extra action.

The tooltip icon is added to the label of a form group or to the legend of a fieldset.[tooltip]=You will receive an errror when saving if your username is already taken.

By default all message codes allow HTML entities, so you can add additional links or markup to them.

In case of a form group you can also manually set the different text components from code. Values set from code will take precedence and will never be replaced by the values resolved from message codes.

A more detailed explanation of how message codes are resolved and which codes are possible can be found in the appendix.

1.5. custom views

TODO: register menu item, register access validator === AdminWebModule JQuery plugins :chapter-number: 0 The default EntityModule web resources add some JQuery based javascript plugins.

1.5.1. EntityModule object

All EntityModule and BootstrapUiModule javascript can be initialized by calling EntityModule.initializeFormElements(). This method optionally takes an argument that is the node in which the form elements should be initialized.

This is automatically done on document load, but when using AJAX fragment rendering, you usually want to re-initialize the DOM element that was updated.

Custom initializers

You can easily add a custom initializer function by adding it with EntityModule.registerInitializer( callback ). There is no need to manually execute your callback on document load, as that will happen automatically by the EntityModule.

Don’t execute your callback on document load and then add it to the initializers. Execution will happen automatically when calling registerInitializer().
Example registering a custom initializer that configures a sortable table to use AJAX loading
EntityModule.registerInitializer( function( node ) {
    $( '[data-tbl-type="paged"]', node )
        .on( "emSortableTable:prepareData", function( e, params ) {
            console.log( "enhancing the data", params );
            params['_partial'] = 'content';
        } )
        .on( "emSortableTable:loadData", function( e, params ) {
            console.log( "performing ajax load" );

            $.get( '#', $.param( params, true ), function( data ) {
                   $( '.pcs' ).replaceWith( data );
                   // initialize the form elements in the element just updated
                   EntityModule.initializeFormElements( $('.pcs') );
        } )
} );

1.5.2. Sortable tables

The default list views support paging and sorting of the pages, where client-side code is used to trigger reloading of the page.

Every table matching the selector [data-tbl-type="paged"] will be initialized for sorting and paging. A sortable table considers 3 default parameters:

  • page: page number

  • size: number of results a single page should have

  • sort: array of sort strings: field,direction (eg. name,ASC)

If a table is bound to a form (specified by the data-tbl-form attribute), paging or sorting will result in that form being submitted with the parameters being added as hidden form elements. If no form is bound, the current URL will be reloaded and the parameters added to the query string.

Customizing behaviour

You can hook into the default behaviour by using the events a sortable table emits or listens to.

Event Description Argument


Trigger this event if you want to reload the data for a specific page.

Page number.


Trigger this event if you want to reload the data with different sorting. If the data is already sorted on the field specified, the sort order will be reversed.

Name of the field to sort on. Usually value of the data-tbl-field attribute.


Called after determining page number, result size and fields to sort on. Subscribe to this event if you want to expand or modify the parameters that should be submitted.

Parameter map containing: page,size and sort keys.


Called after the parameters for the data have been prepared. Subscribe to this event if you want to provide a custom method of fetching the data (eg AJAX based). Note that you have to prevent the default execution if you provide your own mechanism.

Parameters that should be used for fetching the data.

Manually creating sortable tables

If you want to manually initialize a sortable table you can directly call the JQuery plugin emSortableTable() on any element.

You can easily create the valid structure for a sortable table using the EntityViewElementBuilderHelper. This allows you to create a SortableTableBuilder that builds a ViewElement that renders the right markup including all data attributes.

A valid sortable table requires several data attributes to be present on the DOM element:

Attribute Description


Unique id of the data table. Also used on column headings and pager control elements to specify the table they belong to.


(Optional) Name of the form that should be submitted when reloading the table data.


Total number of pages in the result set.


Single page size.


Current page number (0 based).


Current sort value. This is a JSON object structure containing the actual sort fields and their order. Depending on the the presence of custom Sort.Order.class attributes on the EntityPropertyDescriptor these field names will be the same as the property names.


Only used on heading cells that should be sortable. Specifies the field this column represents.


Only used on heading cells that should be sortable. Contains the actual property name that is being sorted on. Usually the same as the field name.


Used on a pager text field that contains the page number.


Used on any element that should navigate to a page on a click event. Contains the value of the page that should be navigated to.

1.6.1. Default views

If AdminWebModul is enabled and you have an auto-detected entity type (eg. from a Spring Data repository), the default views are usually enabled. Every view has a base URL path, see the chapter default views for more information on the view types.

Table 1. Default view paths
View name Path











Not all paths work in all situations. The path structure is fixed, but it is only active if the corresponding view is also registered.
Association views

The default view path for an association depends on the type of association. A LINKED association will use the same path as the target entity type, usually with the exception of the listView. An EMBEDDED association has a path structure nested below the source entity path.

Table 2. Embedded association view paths
View name Path











Custom view

You can easily register a custom view for an entity type using an EntityConfigurer. Rendering that view can be done by calling the base entity type or association url, and specifying the view query string parameter.

The path segments past the entity type root do not actually matter, you can use any of the default view paths as a base. The base type of view being rendered (eg. list view, form view) depends solely on how you defined the view itself.

Suppose you created a custom update form view with name myCustomView. The following paths would render the exact same view:

  • @adminWeb:/entities/{entityName}/{id}?view=myCustomView

  • @adminWeb:/entities/{entityName}/{id}/update?view=myCustomView

An EntityViewLinks component is available to generate links to entity views from anywhere in your code. This component will inspect the EntityRegistry to find corresponding configurations and determine how links should be built.

Using EntityViewLinks central component
EntityViewLinks links;

// link to a list view
links.linkTo( MyEntity.class ).toUriString();

// Get the separate link builder
EntityViewLinkBuilder linkBuilder = links.linkTo( MyEntity.class );

Apart from using the EntityViewLinks component globally to build links, the relevant EntityViewLinkBuilder for a particular entity type can often be accessed directly:

  • It is registered as an EntityConfiguration attribute of that entity type. You can retrieve it using entityConfiguration.getAttribute( EntityViewLinkBuilder.class ).

  • When rendering an entity view, the builder is available as the linkBuilder property on the current EntityViewContext.

  • The EntityAdminMenuEvent exposes the builder for the current entity type as a linkBuilder property, for direct access when building menus.

EntityViewLinkBuilder examples
String url;
EntityViewContext entityViewContext;
MyEntity entity;

// -- Retrieving the link builder for the entity type
EntityViewLinkBuilder linkBuilder = entityViewContext.getLinkBuilder();

// -- Linking to the list view
// url: /entities/myEntity
url = linkBuilder.listView().toUriString();

// -- Linking to the create view
// url: /entities/myEntity/create
url = linkBuilder.createView().toUriString();

// -- Linking to the update view
// url: /entities/myEntity/1/update
url = linkBuilder.forInstance( entity ).updateView().toUriString();

// -- Linking to a custom view
// url: /entities/myEntity/1?view=customViewName
url = linkBuilder.forInstance( entity )
                 .withViewName( "customViewName" )

// -- Linking to an association list view
// url: /entities/myEntity/1/associations/associatedItems/
url = linkBuilder.forInstance( entity )
                 .association( AssociatedItem.class )

// -- Linking to a single associated item update view
// url: /entities/myEntity/1/associations/associatedItems/2/update
url = linkBuilder.forInstance( entity )
                 .association( associatedItem )

The EntityViewLinkBuilder has a fluent API that allows customizing the URL before converting it to a String. It also has some short-hand methods for commonly used entity view related parameters. Every method call results in a new instance being created, so you will not make inadvertent changes to an existing link builder.

Useful methods of EntityViewLinkBuilder
// append a custom path segment
.slash( String path )
// append query parameter
.withQueryParam( String param, Object... values )
// set a from URL ('from' query parameter)
.withFromUrl( String url )
// set a partial fragment ('_partial' query parameter)
.withPartial( String fragment )
// set a custom view name ('view' query parameter)
.withViewName( String viewName )

// return the unprocessed URI (eg. '@adminWeb:/entities/myEntity')
// return the processed URI (eg. '/admin/entities/myEntity')
// create a new UriComponentsBuilder with the current settings
// return as URI
// return as UriComponents

// return the original EntityViewLinks

1.6.3. Common URL parameters

The following is a list of query string parameters often used with entity views:


Can hold a URL that should be used as a target when the new operation completes. Most often this is the target of the cancel link on a form view. See also EntityViewLinkBuilder#withFromUrl(String).

When building association links, a default from value to navigate back to the original entity will usually be added.


This can be the identifier of the only fragment of a page that should be rendered. Partial view rendering is part of the Across Web features. See also EntityViewLinkBuilder#withPartial(String).


Name of the specific custom view that should be rendered. See also EntityViewLinkBuilder#withViewName(String).

2. Entity associations

The EntityModule attempts to automatically detect related entities and creates associations mainly to facilitate UI generation. Currently @OneToMany, @ManyToMany and @ManyToOne annotations from javax.persistence API are all scanned and used to build EntityAssociation entries.

In the administrative UI the management of related entities can often be done either through the property or the association. This is especially the case for @ManyToMany and @OneToMany associations that are mapped through a property with collection type. By default related entity management will be done through the property and the association will be generated but hidden.

If you want to enable management through the association interface, you should manipulate the hidden property of both the association and the property using an EntityConfigurer.
public void configure( EntitiesConfigurationBuilder configuration ) {
    // Groups should be managed through the association instead of the property
    configuration.withType( MachinePrincipal.class )
                 .properties( props -> "groups" ).hidden( true ) )
                 .association( ab -> "machinePrincipal.groups" ).show() );

2.1. Association type

Every EntityAssociation is of a specific type, configured through the associationType property. The association type determines how the associated values can be managed through the user interface.

The following association types are possible:

Association type Behaviour


The related entities are only linked to. The tab of the parent entity shows the list of related entities, but any modify action will navigate away from the parent entity.

This is the appropriate type if your related entities can in turn have other related entities. Usually this also means the related entity type has a main menu item.


The related entities will be managed through the tab of their parent entity. Modifying a related entity will be displayed as modifying the parent entity, no action will leave the context of the parent entity.

Use this type if the related entities only exist if their parent exists. Usually the related entity type does not have any menu item, nor sub tabs (the latter would not be displayed).

By default an association is of type LINKED.

2.2. ParentDeleteMode

An EntityAssociation has a parentDeleteMode property that determines how associated items will influence the ability to delete in the user interface. The default value is SUPPRESS but can be set through the EntitiesConfigurationBuilder.

For more information see the delete view chapter.

2.3. Association naming and location

Associations are added to the EntityConfiguration for which it makes most sense to manage them from a UI perspective. The association naming however is done according to the entity class and property names.


  • entity Group

  • entity User has a one to many with Group on property groups

  • association user.groups will be created on the entity configuration of Group

2.4. Customize associated entity creation

You can customize the creation of an associated entity in the form views, by setting a custom EntityFactory. This is especially useful for manually creating associations.

An EntityAssociation can have an EntityFactory.class attribute set that contains the EntityFactory that should be used for creating associated items. If no factory is set as attribute on the association, the default EntityFactory of the target configuration will be used.

If there is an EntityFactory attribute set on the association, that factory will be used when creating a new associated entity instance. The createNew() factory method will get called with the parent entity (for whom an associated item is being created) as single parameter.

3. Integration with other modules

3.1. AdminWebModule

If the AdminWebModule is present entity management controllers will be created for all registered entity configurations. If you want to avoid the automatic registration of entity management controllers for a particular entity type, you should set the EntityConfiguration as hidden. This will effectively disable the default entity controllers for that type, and hide the existence of the entity type from the administration interface.

You can also hide one or more associations. By default an association will not be shown if one of the participating entities is hidden. If you specify the hidden property of an EntityAssociation explicitly, that value will take precedence of the entity configurations. This way it is possible to generate management pages for associated entities, but not for the main entity type.

3.2. AdminWebModule: developer tools

When integrated with AdminWebModule and development mode is active, an entity registry browser will be added to the Developer tools section of the administration ui. The browser allows you to inspect the registered entities along with their views, associations and properties.

3.3. Auditable

If SpringSecurityModule is present, EntityModule adapts the default views for Auditable entities. The createdBy and lastModifiedBy properties are rendered using an AuditablePrincipalPropertyViewElementBuilder which uses the SecurityPrincipalLabelResolverStrategy to generate a pretty label for a principal (eg. full name instead of username). The default properties are removed from default views, but an aggregated property created and lastModified is added. The aggregated properties combine both the timestamp and the principal in a single property using the AuditablePropertyViewElementBuilder.

See the AuditableEntityUiConfiguration for full customization.

Customizing generated Entity views

The following section gives an overview of common customizations for generated entity views.

1.1. Customizing the EntityViewFactoryBuilderInitializer

1.2. Changing entity names, property names or other labels

All entity names, property names and labels can be customized using message sources. For an explanation of the different message codes used, see the relevant appendix.

See also the section on configuring form controls text for common customizations.

1.3. Setting page title or changing page layout

Setting a page title can be done by adding the corresponding message code. All default views automatically add a page title (optionally with sub text) if the corresponding message code resolves a non-empty string.

See the message codes appendix for a list of relevant message codes.

Changing the page layout

Entity views use a PageContentStructure for the base structure of the web page. The PageContentStructure is available as a request scoped bean, but can also be retrieved from the EntityViewRequest.

See the AdminWebModule reference documentation for a basic explanation of PageContentStructure.

Modifying the page layout for all (or a selection of) views

If you want to modify page layout for multiple views at runtime, you can subscribe to the EntityPageStructureRenderedEvent. This event is published during the postRender() phase and gives you context of the view that is being rendered, allowing you to make changes outside regular EntityViewProcessor implementations.

SingleEntityPageStructureViewProcessor and ListPageStructureViewProcessor are the view processors responsible for building the basic page structure and publishing the event.

1.4. Specifying a custom template

Every default view uses a specific (Thymeleaf) template that renders the ViewElement list created by the view. If you want control over the rendering through a separate template you can specify a different template using the template() method on the EntityViewFactoryBuilder.

1.5. EntityViewProcessor

Modifying a default view can be done by registering an EntityViewProcessor for that view. This API allows you to modify the ViewElement collection that should be generated. This is a useful hook to add for example custom form elements that you wish to add and process. If can also be used to reorganize the layout of the form from backend code using the ContainerViewElementUtils.

1.6. Using a custom EntityViewFactory

Full control can be done by registering a custom EntityViewFactory implementation.

1.7. Selecting properties

EntityPropertySelector, incremental builders, keep current, select all, select all without default filter, exclude

1.8. Configuring property view types

You can configure a property using the EntityPropertyDescriptorBuilder. This builder also contains some methods to influence the ViewElement that should be built for that property for a given mode.

By default a ViewElement will be built based on the property and some of its annotations. There are 3 ways you can influence the default behaviour:

  • specify a custom viewElementType() for a given mode

    • a default builder of that type will be created for that mode

  • specify one or more viewElementPostProcessor() for a given mode

    • these ViewElementPostProcessor instances will be added to the default builder, in the order they were registered

  • specify a custom viewElementBuilder() for a given mode

    • the default building will be ignored and only your custom builder will be used

1.9. Fieldset properties

A fieldset is a visual grouping of other properties, inside a block that has a title (legend) and optional description. Fieldsets are rendered as a FieldsetFormElement. You can postprocess a group of ViewElement instances and move them manually to a FieldsetFormElement, or you can set a fieldset as the ViewElementMode for a property.

In the latter, because a fieldset is a collection of other properties, you will need to specify which properties make up the fieldset. Specifying the properties of fieldset is done by setting the EntityAttributes.FIELDSET_PROPERTY_SELECTOR to a valid EntityPropertySelector.

The following is an example of manually adding a fieldset property to a form, and moving some properties to it:

entities.withType( WebPage.class )
        .createOrUpdateFormView( fvb -> fvb
                 * First create a new property that is a fieldset
                 * of the existing url and urlGenerated properties.
                 * We add this property only to the scope of the
                 * create or update form view.
                .properties( props -> props
                        .property( "url-settings" )
                        .displayName( "URL settings" )
                        .viewElementType( ViewElementMode.FORM_WRITE, BootstrapUiElements.FIELDSET )
                                EntityPropertySelector.of( "url", "urlGenerated" )
                 * Because url and urlGenerated are direct members
                 * of WebPage, we need to ensure they are not rendered
                 * directly anymore, so we remove them from the form view.
                 * The new url-settings property will be selected by default
                 * and in turn will render the url and urlGenerated properties.
                 * If we were to configure the url-settings property as hidden,
                 * we would have to explicitly include it in the form view as well.
                 * That would probably be a preferred approach if we have defined
                 * url-settings in the global property registry for WebPage.
                .showProperties( "*", "~url", "~urlGenerated" )
Properties mapped to an @Embedded type will automatically be mapped as a fieldset type.

1.10. Customizing entity validation

By default annotation validation is performed on all entities. Customizing validation can be done by simply specifying a Validator bean that supports the specific entity type. You can use the EntityValidatorSupport as a base class to extend the default annotation based entity validation.

If more than one Validator could be applied, you will manually have to set the Validator.class attribute on the EntityConfiguration to the correct one.

1.11. Customizing VALUE mode elements

The ViewElementMode.VALUE and ViewElementMode.LIST_VALUE are the defaults to provide the output of a property for readonly views. Unless a specific ViewElement is configured, this will always be a String output of the property. By default the mvcConversionService will be used to convert the property value if no type specific builder is provided.

Apart from providing a custom ViewElement you can also modify the rendered output by providing attributes on the EntityPropertyDescriptor. If you provide a org.springframework.format.Printer.class attribute, that implementation will be used for printing the text value. Alternatively you can provide a java.text.Format.class attribute to be used. Note that most default Format implementations are not thread-safe, in that case you should wrap them in a SynchronizedFormat instance.

All standard view elements will use the Printer or Format attribute if one of them is present, instead of the default. A Printer attribute takes precedence over a Format.

1.12. Customizing textbox elements

TextboxFormElement.Type can be set as an attribute on the EntityPropertyDescriptor. If set and the property is generated as a TextboxFormElement, that type will be used.

You can add default post processors to the TextboxFormElementBuilderFactory to customize the autodetection.

1.13. Customizing numeric elements

By default all Number type properties will result in a NumericFormElement being used which is rendered as a textbox. The behavior can be customized by providing a NumericFormElementConfiguration. A default configuration will only be created for properties annotated with a Spring @NumberFormat for type CURRENCY or PERCENT, if no NumericFormElementConfiguration.class or NumericFormElementConfiguration.Format.class attribute is present.

If a NumericFormElementConfiguration is present a more advanced javascript control will be used in the front-end for value input. The same configuration will also be used for rendering the VALUE mode elements, formatting the output according to the properties configured.

Manually configuring percent

Put a format attribute with value PERCENT on the EntityPropertyDescriptor. This will create a locale specific percentage format with 2 decimals (unless the property type is integer). Alternatively use the static NumericFormElementConfiguration.percent() factory method to quickly create a localizable format suitable for percentages.

If you use Spring number format for PERCENT then 1 is expected to match 100%. If you manually create a NumericFormElementConfiguration it expects 100 to match with 100%. You can modify this behavior by setting the multiplier property on the configuration.
Manually configuring currency

The easiest way to configure a currency is to set a Currency.class attribute for the property. In that case a locale specific format for that currency will be created. Alternatively the same options as for percentages can be used and there is a NumericFormElementConfiguration.currency() factory method available.

1.14. Customizing datetime picker elements

By default all Date properties will result in a DateTimeFormElement which is rendered as a date time picker. The form element can be customized through the DateTimeFormElementConfiguration class. The default configuration is determined based on the presence of @Temporal annotations on the property. The date picker supports 3 major modes: date, time and timestamp (date + time) with minutes being the maximum resolution. The presence of @Past and @Future validation annotations will additionally restrict the dates that are selectable.

A specific date picker format can easily be specified by putting a DateTimeFormElementConfiguration.Format attribute. Advanced customization can be done by setting a complete DateTimeFormElementConfiguration as attribute. Dynamic configuration (for example setting the first selectable date relative to the current date) can only be done by specifying a DateTimeFormElementBuilder manually and adding a custom post processor that modifies the DateTimeFormElementConfiguration. A DateTimeFormElementConfiguration is always duplicated when creating an element so it is safe for post processors to modify the instance.

Using dates with TemporalType.TIME and JPA

A property of type java.util.Date but annotated with @Temporal(TemporalType.TIME) will result in only time selection being available (hours and minutes). However the @Temporal annotation also influences how JPA will persist the data type. If your type was created as a timestamp in the database schema, this might result in conversion errors. With Hibernate you can resolve this by additionally specifying a @Type annotation forcing the type to be persisted as timestamp.

Example of a required time property that is written as a date relative to start of epoch time in the database
@Column(name = "arrival_time")
@Type( type = "timestamp")
private Date arrivalTime;

1.15. Customizing selectable options

Any entity or enum property will by default be rendered via an OptionsFormElementBuilder resulting in either a select box or list of checkboxes being rendered.

Set the type of options control

You can customize the type of options control to be generated by setting the viewElementType for a property.

entities.withType( WebPage.class )
    .createOrUpdateFormView( fvb -> fvb
         * Render the state as radio buttons instead of a select box.
        .properties( props -> props
            .property( "state" )
            .viewElementType( ViewElementMode.CONTROL, BootstrapUiElements.RADIO )

If no viewElementType has been specified, a default type will be determined: a select box will be used in case of a single value, a checkbox list in case of multiple values.

Advanced select box configuration

A select control being generated will be a bootstrap-select with default configuration. You can customize the select box configuration by manually setting a SelectFormElementConfiguration attributes.

See the BootstrapUiModule documentation for all configurable properties.

If no viewElementType has been specified, but a SelectFormElementConfiguration attribute is present, the resulting control will be a select box.

Configuring options that can be selected

You can manipulate the options that can be selected in several ways by setting either EntityConfiguration or EntityPropertyDescriptor attributes.

If your property is another entity type, by default the selectable options will be all entities of that type. If you want to change this for all properties of that type, you can set either an OptionGenerator.class, OptionIterableBuilder.class or EntityAttributes.OPTIONS_ENTITY_QUERY attribute on the target EntityConfiguration. If you want to change it only for a single property, you can configure the same attributes on the EntityPropertyDescriptor of that property.

entities.withType( WebCmsArticle.class )
    .createOrUpdateFormView( fvb -> fvb
         * Only allow published sections to be selectable,
         * by specifying an EQL statement to fetch them.
        .properties( props -> props
            .property( "section" )
            .attribute( EntityAttributes.OPTIONS_ENTITY_QUERY, "published = TRUE ORDER BY name ASC" )

When dealing with an enum type, you can also configure the EntityAttributes.OPTIONS_ALLOWED_VALUES with the `EnumSet`of selectable options.

 * Limit the selectable enum HTTP status.
entities.withType( WebCmsUrl.class )
        props -> props
            .property( "httpStatus" )
                EnumSet.of( HttpStatus.OK, HttpStatus.NOT_FOUND )
Depending on the attribute you will change more of the default behaviour and will have to provide custom implementations. Use the most appropriate attribute for your use case. See the appendix for more information on the different attributes. :!sectnums:

Configuring entity types

1.1. Using builders

Entities are usually automatically added to the EntityRegistry through the use of one or more EntityRegistrar beans. The registrars will apply a default configuration, usually consisting of all properties, associations and views.

Customizing the EntityRegistry is done by implementing one or more EntityConfigurer beans in your modules. These receive an EntitiesConfigurationBuilder that effectively allows you to customize all registered EntityConfiguration instances. Multiple EntityConfigurer beans can modify the same EntityConfiguration, the order in which they are applied will determine the last value if they modify the same properties.

Investigate the javadoc of the EntitiesConfigurationBuilder and child builders to discover all possible configuration options.

@AcrossDepends(required = "EntityModule")
public class UserEntitiesConfiguration implements EntityConfigurer
        public void configure( EntitiesConfigurationBuilder configuration ) {
                // By default permissions cannot be managed through the user interface
                configuration.withType( Permission.class ).hide();

1.2. Configuring properties

Properties for an entity can be configured through the builders as well. New properties can be added or the default properties can modified. How properties are configured determines how they will be rendered on the generated forms.


A hidden property will by default not be returned when requesting all properties from an EntityPropertyRegistry. You can still get this property directly however, the hidden state means a property will not advertise itself, you must know of its existence.


Any readable property can be rendered in all views. This state means that a form control can always be generated, even though it might very well be readonly if the property is not writable.


A writable property can be rendered in form views. In case a property is writable but not readable, the property can only be included in forms but not in other views.

The hidden state has no correlation with a hidden form control. Setting a property to be rendered as a hidden form control can only be done through configuring the right ViewElement information for that property.

1.2.1. Configuring a label

An entity with a corresponding EntityConfiguration always has a label, this is a textual representation of the entity in for example lists. This could be the name or the * title* property for example. By default the label corresponds to a custom generated property #label that defaults to calling toString() on the entity.

You can configure the label using the label() method on a PropertyDescriptorBuilder. This is equivalent to calling property("#label"). If you want to use another property as the base for label generation, you can configure this on the EntityConfigurationBuilder by calling label("propertyName"). This will copy all settings from the source property to the #label property, but keep in mind it still is a separate property that can be customized.

public void configure( EntitiesConfigurationBuilder entities ) {
    // Configure the username to be used as label for a User entity
    entities.withType( User.class ).label( "username" );

    // Configure the group name to be used as base label, but modify the value fetcher so
    // the label is prefixed with Group
    entities.withType( Group.class )
            .properties( props -> props.label( "name" ).spelValueFetcher( "'Group: ' + name" ) );

If you do not wish to use the #label property at all as default entity label, you can customize the Printer used for label generation by modifying the EntityModel.

As #label is a generated property, sorting is not enabled by default. If you configure the label using an existing property, the sortable attribute will be copied as well and sorting on label will be possible.

1.3. Creating an EntityConfiguration manually

1.3.1. Attributes to configure

Some attributes are mandatory, others are optional but will often impact how much functionality is available out of the box. You can configure any attribute you like, see the section on automatic registration for a list of common attributes provided by other registrars.

1.3.2. EntityQueryExecutor

In order for generated views to work automatically, an EntityConfiguration should have an EntityQueryExecutor attribute. The EntityQueryExecutor is a generic interface that supports the simple EntityQuery abstraction for fetching entities from the backing repository. Default implementations exist for JpaSpecificationExecutor and QueryDslPredicateExecutor.

1.3.3. Registering an ENUM as entity

EntityModule supports registration of enums as EntityConfiguration. When creating an EntityConfiguration for an enum, a basic EntityModule will get built and all enum properties will be configurable.

Registering enums as entity is mainly useful for configuration of display properties in related entity views.

Example registering an enum as entity
// Enum class
public enum Country
    BE( "Belgium" ),
    UK( "United Kingdom" ),
    NL( "Netherlands" );

    private String name;

    Country( String name ) { = name;

    public String getName() {
        return name;

// Create the EntityConfiguration
public void configure( EntitiesConfigurationBuilder entities ) {
    entities.create().entityType( Country.class, true ).label( "name" );

1.4. Automatic registration of entity types

1.4.1. Spring Data repositories

JPA repositories


  • Embedded ID

  • register conversion service

  • add json serializer

Automatic attribute registration

EntityQuery infrastructure

EntityModule provides an abstraction layer for querying entities. This abstraction is built around the concept of an EntityQuery.

This chapter gives some insight in the setup and how to customize if so wanted.

1. EntityQueryExecutor

The EntityQueryExecutor is responsible for executing an EntityQuery and returning the entities requested. An EntityConfiguration can have a single EntityQueryExecutor.class attribute holding the executor instance.

Default implementations exist for JpaSpecificationExecutor and QueryDslPredicateExecutor. This means that any entity configurations having a repository of this type will get an EntityQueryExecutor created automatically.

Since the EntityQueryExecutor is backed by a specific repository implementation, supported functionality also depends on the actual backing repository.

The presence of the EntityQueryExecutor is a requirement for the default entity views.

1.1. AssociatedEntityQueryExecutor

Like EntityQueryExecutor that is registered on the EntityConfiguration, every EntityAssociation can have an AssociatedEntityQueryExecutor registered. The AssociatedEntityQueryExecutor allows executing queries in the context of a single parent object.

Like the EntityQueryExecutor, the AssociatedEntityQueryExecutor is usually added automatically.

The presence of the AssociatedEntityQueryExecutor is a requirement for the default association entity views.

2. EntityQuery Language (EQL)

Apart from building an EntityQuery in code, one can be specified using the EntityQuery Language (EQL). EQL provides an SQL-like syntax for building queries. Some examples could be:

  • name = 'john'

  • order by birthday asc

  • name = 'john' order by birthday desc, registrationDate asc

  • name = 'john' and birthday >= '1980-01-01'

  • (name in ('john', 'jane') and birthday = today()) or birthday is EMPTY order by name asc

  • children contains 'john' or children is EMPTY

A single clause of a query consists of a field, followed by an operator, followed by one or more values or functions. Multiple values for one field are grouped together inside parentheses. Clauses can be combined using either and or or. Operator precedence can be enforced by using parentheses around a clause.

A field of a query clause usually corresponds with a single property you want to query.

A SQL-like order by clause specifying one or more properties is also supported. An EQL statement with only an ordering clause will select all items in that order.

Special characters in string literals should be escaped using the \\ (backslash) character. In case of like conditions, they should be double-escaped.

2.1. Supported operators

2.1.1. Equality operators

Usable on most property types.

Operator Description




not equals

2.1.2. IN/NOT IN

Usable on most property types. These are the only operators that take a group of values as argument.

Operator Description


equal to any of the argument values


not equal to any of the argument values

2.1.3. Comparing operators

Usable on numeric and date property types.

> greater than


greater than or equal to


less than

less than or equal to

2.1.4. LIKE operators

Usable on string property types. The argument specifies the pattern that property value should match. The pattern can contain simple wildcard specified using %.

Operator Description


should match the pattern specified as argument


should not match the pattern specified as argument


case insensitive match of the pattern specified as argument


should not match the pattern specified as argument (case insensitive)

NOTE Special characters in LIKE statements should be double-escaped using the \ (backslash) character. This includes the literal % (percentage) character.

Case insensitive property matching When using ILIKE conditions, a case insensitive lookup will be performed in the datastore. If you want to force a property to always have a case insensitive lookup, you can do so by configuring a EntityQueryConditionTranslator attribute on that property.

 .withType( Group.class )
    props -> "name" ).attribute( EntityQueryConditionTranslator.class, EntityQueryConditionTranslator.ignoreCase() )

This will convert any equality or like operator to the equivalent case insensitive lookup.

WARNING Whenever possible, it is probably best to use collation settings of your datastore to ensure case insensitive querying of properties. Configuring it on a datastore level will almost certainly give you much better performance. If collation is not possible, investigate the option of using function indices on the relevant columns.


Usable on collection or text property types. In case of text the contains statement is translated to a like statement with wildcards before and after.

Operator Description


argument should be present in the collection or string should be present in the text


argument should not be present in the collection or string should not be present in the text


Usable on single value properties only. These operators to not take any additional arguments.

Operator Description


property should not be set (null)


property should be set (not null)


Preferred for collection type properties, altough usually will work as an alternative for IS NULL/IS NOT NULL on single value properties. These operators to not take any additional arguments.

Operator Description


property should not have any members (in case of collection) or should not be set (if single value property)


property should have at least one member (in case of collection) or should be set (if single value property)

2.2. Default EQL functions

Security related functions

Function Description


returns the name of the current authenticated principal

Date and time functions

Function Description


returns current timestamp


returns date of today

2.3. EntityQueryParser

The EntityQueryParser is responsible for converting an EQL statement into a valid EntityQuery. Any entity configuration with an EntityQueryExecutor registered will have an EntityQueryParser created automatically.

The parser will validate the EQL statement and convert it to a strongly typed EntityQuery. The default EntityQueryParser uses the entity related EntityPropertyRegistry to validate the query clauses.

3. EntityQuery filtering on list view

Any entity configuration having an EntityQueryParser and EntityQueryExecutor can enable an EntityQuery based filter on its list views. This can be done through the entityQueryFilter() method on the `EntityListViewFactoryBuilder.

An EntityQuery filter supports 2 different modes:

  • basic mode allows you to select the property values to filter on using form controls

  • advanced mode give you a textbox for entering any EQL statement to use as filter

By default only advanced mode is active. Basic mode is activated if you configure the properties that should be shown in the filter. You do so by modifying the EntityQueryFilterConfiguration that is being used.

Activating the default (advanced mode) EntityQuery filter

entities.withType( Group.class )
        .listView( lvb -> lvb.entityQueryFilter( true ) );

Activating basic mode + advanced mode EntityQuery filter

entities.withType( Group.class )
        .listView( lvb -> lvb.entityQueryFilter( true )
                             .showProperties( "name", "active" ) );

By default both basic and advanced mode are available, and the UI allows switching between them. All options are configurable on the EntityQueryFilterConfiguration.

3.1. Basic mode

Basic mode enables the use of controls to filter by and will parse the content of the property controls to a valid EQL statement which will then be submitted.

By default the following controls will be created - depending on property type: * textbox controls * select controls

For select controls, you can specify if multiple values can be selected on the EntityQueryFilterConfiguration.

Text controls will by default use the EntityQueryOps.CONTAINS operand, multi value controls will use the EntityQueryOps.IN operand and otherwise the EntityQueryOps.EQ operand will be used if none was specified on the property directly.

For easier switching between basic and advanced mode, it is also possible to define an EntityAttribute.OPTIONS_ENHANCER on the property, which allows to define the value to be used for the object (e.g. instead of the id of a group, i’d like to see the name of the group whilst filtering). An EntityQueryValueEnhancer however merely defines a label to use. For the statement to be parsed successfully you will also need to register a corresponding Converter on the ConversionService.

The values of the filter controls will be set using the EntityQueryRequest and EntityQueryRequestValueFetcher.

3.2. Advanced mode

Advanced mode enables the use of EQL to filter the current view using a simple textbox. If both advanced and basic mode are allowed, and the EQL statement that was last executed is not convertible to basic mode, basic mode will be disabled.

Example EntityQuery filter configuration

entities.withType( WebCmsArticle.class )
            lvb -> lvb.entityQueryFilter(
              eqf -> eqf.showProperties("title", "articleType") // create a control for title and articleType
                        .multiValue("articleType") // It should be possible to filter on multiple article types

4. EQL predicate on list view

List views also support a base predicate to be configured as an EQL statement. This base predicate will always be applied to the query being executed if it uses the DefaultEntityFetchingViewProcessor or the EntityQueryFilterProcessor.

Ensure deleted (flag) items are never shown

entities.withType( Group.class )
        .listView( lvb -> lvb.entityQueryPredicate( "deleted = false" )        );

Like EQL based filtering, this requires the entity configuration to have a valid EntityQueryExecutor infrastructure.

5. Extending EQL

The EntityQuery infrastructure provides some hooks you can use to extend the EQL support with application specific methods.

5.1. Custom value conversion

When converting an EQL query all value arguments are first converted to an EQType representation before being converted into their respective Java type. Actual type conversion is then done via the Spring ConversionService. To create a custom conversion you can simply register a Converter that converts from the relevant EQType to the property type.

The following table shows how EQL arguments will be converted to their respective EQType:

Argument value EQType


EQValue: name


EQString: name

(name, 'name')


- EQValue: name

- EQString: name

users(name, 'name')

EQFunction: users


- EQValue: name

- EQString: name

By default EntityModule registers id-based lookups for all its registered entities. So supposing you have an entity User with id 1 and you want to query on a property creator of type User, the following query would work: creator = 1.

When building the EntityQuery the value 1 would be used as the id to find the User instance, and the latter would be used as the argument for the final query. If we want to replace the custom behavior and allow the user to be specified by username instead, we could easily register a custom converter.

public class EQValueToUserConverter implements Converter<EQValue, User>

    public User convert( EQValue source ) {
        return userRepository.findByUsername( source.getValue() );


converterRegistry.addConverter( new EQValueToUserConverter(...) );

This would allow us to execute the queries like creator = john or creator in (john, jane). Any type-specific converter will take precedence over the defaults.

NOTE The example above would only work if the username can never contain any whitespace. If it can, then we would have to specify it as a String instead and write a converter for EQString instead of EQValue.

5.2. Adding custom functions

An EQL function is represented by a unique name and can optionally take a number of arguments for its execution. Adding custom functions is as easy as simply defining a @Component that implements the EntityQueryFunctionHandler interface. All components of this type will be detected and checked when executing an EQL query.

The handler will be called with the required contextual data for the return type requested. If you want to use a function to compare a property that has a Date type, your function should return a Date instance as well.

A single handler can support multiple functions and requested return types.

Simple EntityQuery function that always returns the String hello

 * Simple EntityQuery function that always returns the String 'hello'.
 * Example eql: name = hello() or name in (hello(), 'goodbye')
public class HelloFunction implements EntityQueryFunctionHandler
        public boolean accepts( String functionName, TypeDescriptor desiredType ) {
                return "hello".equals( functionName );

        public Object apply( String functionName,
                             EQType[] arguments,
                             TypeDescriptor desiredType,
                             EQTypeConverter argumentConverter ) {
                return "hello";

5.3. Custom EQL translation

You can register an EntityQueryConditionTranslator attribute on any entity property. If a translator instance is present, it will be called during the parsing phase of an EQL statement into an EntityQuery.

Ensuring a field search is always case insensitive

 .withType( Group.class )
    props -> "name" ).attribute( EntityQueryConditionTranslator.class, EntityQueryConditionTranslator.ignoreCase() )

Define a search text property that actually searches on other fields

configuration.withType( Note.class )
             .properties( props -> "text" )
                                        .valueFetcher( entity -> "" )
                                        .propertyType( TypeDescriptor.valueOf( String.class ) )
                                        .viewElementType( ViewElementMode.CONTROL, BootstrapUiElements.TEXTAREA )
                                        .attribute( EntityQueryConditionTranslator.class,
                                                    EntityQueryConditionTranslator.expandingOr( "name", "content" ) )
                                        .hidden( true )


1. Attributes overview

Some useful (utility) classes for managing attributes for different EntityModule objects:

  • EntityAttributes contains a variation of common attribute constants and some static helper methods

  • EntityAttributeRegistrars contains helper registrar factory methods

  • EntityViewFactoryAttributes contains common attribute constants and helper methods, specific for entity views

EntityConfiguration attributes

The following table lists commonly present attributes on an EntityConfiguration.

Key Value


In case of an entity registered through a Spring Data repository.


In case of an entity registered through a Spring Data repository.


In case of an entity registered through a Spring Data repository.


In case of an entity registered through a Spring Data repository that exposed PersistentEntity information.


Holds the EntityQueryExecutor that can be used for custom EntityQuery execution and will be used by default for fetching entities. Available if the Repository was supported by one of the default EntityQueryExecutor implementations.


Holds the EntityQueryParser that should be used for parsing EQL statements into a valid EntityQuery. Available if the EntityQueryExecutor.class attribute is present.


Optionally holds the name of the PlatformTransactionManager that the repository for this entity uses. EntityModule attempts to detect the transaction manager automatically for every Spring Data repository. When set, this will enable transaction management for the default create, update and delete views.


Holds an EntityViewLinkBuilder for the type represented by the EntityConfiguration. Only available when AdminWebModule is present.


When set on an EntityConfiguration, this will be the default generator used to create the set of options that can be selected for a property that points to the entity configuration. If all you want to configure is the list of possible options, set the OptionIterableBuilder.class attribute instead.


When set on an EntityConfiguration, this will be the default builder used to create the set of options that can be selected for a property that points to the entity configuration.


When set on an EntityConfiguration, contains the EQL statement or EntityQuery that should be used to fetch the selectable options for a property that points to the entity configuration. Will only be used if there is no OptionGenerator.class or OptionIterableBuilder.class attribute set.

EntityPropertyDescriptor attributes

The following table lists commonly present attributes on an EntityPropertyDescriptor.

Key Value


In case of a property of a PersistentEntity registered through a Spring Data repository.


Contains the default Sort.Order if sorting is enabled on this property. By default strings have an order that ignores case.


Optional: required to be a String value. When present this value will be used as the form control name instead of the descriptor name.


When present holds the Java beans property descriptor that was used to create the EntityPropertyDescriptor. The presence of this attribute indicates that the property is not artificial but corresponds to an actual Java class property.


When set on an EntityPropertyDescriptor, this will be the generator used to create the set of options that can be selected for that property. If all you want to configure is the list of possible options, set the OptionIterableBuilder.class attribute instead.


When set on an EntityPropertyDescriptor, this will be the builder used to create the set of options that can be selected for that property.


When set on an EntityPropertyDescriptor, contains the EQL statement or EntityQuery that should be used to fetch the selectable options for that property. Will only be used if there is no OptionGenerator.class or OptionIterableBuilder.class attribute set.


Only applicable if the property is of an enum type. When set, the attribute holds the EnumSet of selectable values. If you want to customize selection of a non-enum type, see the other option related attributes. Will only be used if there is no OptionGenerator.class or OptionIterableBuilder.class attribute set.


Can hold the configuration instance that should be used when generating a select control for this propery. Unless a specific ViewElement type has been specified, this will force the control type generated to be a select as well.


Should be a Boolean value that sets if a control for this property should be marked as required or not.

EntityViewFactory attributes

The following table lists commonly present attributes on an EntityViewFactory.

Key Value


The registry the view belongs to, either the EntityConfiguration or EntityAssociation instance.


If present, holds the AllowableAction that is required for accessing this view.


In case of an entity registered through a Spring Data repository that exposed PersistentEntity information.


Name of the view under which it is registered in the EntityViewRegistry.


Optionally contains a Consumer<EntityAdminMenuEvent> for creating a menu item for that view. See EntityAttributeRegistrars.adminMenu() variations for helper factory methods.


Optionally contains a BiConsumer<EntityViewFactory, EntityViewContext> that should be used to verify access to the view. Usually the default EntityViewFactoryAttributes.defaultAccessValidator() is set, which inspects the AllowableAction.class attribute.

2. Message codes

Labels are resolved using a message code hierarchy. Simply define one or more message sources specifying the properties you want. Unless custom EntityMessageCodeResolver instances are being used, message codes are generated as follows:

Message code Description


Title of the root menu group for entity management.


Title of the menu group for the entities of that module.


Message code for a single enum value label.

Example: enums.Numbers.ONE

Label for an entity in singular form, for use outside or at the beginning of a sentence.


Label for an entity in plural form, for use outside or at the beginning of a sentence.


Label for an entity in singular form, for use within a sentence. If not explicitly specified, the label is generated based by lower-casing the non-inline version.


Label for an entity in plural form, for use within a sentence. If not explicitly specified, the label is generated based by lower-casing the non-inline version.


Label for a single entity property.


Description text for a property. Used by default for form groups and fieldsets in a writable form configuration. If not empty, description text will be added before the control in a form group, and above the content of a fieldset.


Help text for a property. Used by default for form groups and fieldsets in a writable form configuration. If not empty, help text will be added after the control in a form group, and below the content of a fieldset.


Tooltip text for a property. Used by default for form groups and fieldsets in a writable form configuration. If not empty, the tooltip icon will be added in the label of a form group, and in the legend of a fieldset.


Placeholder text for a property. Will be used for certain controle like textbox.



Description text for a validation error message. Optionally can be suffixed with the specific property name.

Example: UserModule.entities.user.validation.NotBlank, UserModule.entities.user.validation.alreadyExists.username

You would then use errors.rejectValue( "username", "alreadyExists" ); after creating the above message code in your message sources.


Title of the admin menu item for this entity. Defaults to the singular name of the entity.


Name of the General tab. Usually the first tab that is also opened when creating a new entity.


Name of the tab for that association.



Name of the tab for the view with that name (if there is a menu item for that view).


Name of the actions, usually the buttons or links on a page. Often you just want to replace these on a global level.



Title of the page. Supports message code parameters.

Example: UserModule.entities.user.pageTitle.update=Updating {1}: {2}


Additional text that should be added as sub text (small) to the page header. Supports message code parameters.

Feedback message shown for the given feedback type.



Sortable table results and pager text keys.

Example: UserModule.entities.user.sortableTable.resultsFound


Delete view specific messages.

Example: UserModule.entities.user.delete.confirmation


The label for the button to navigate from basic to advanced mode.


The label for the button to navigate from advanced to basic mode.


The placeholder for the eql statement filter.


The label for the entity query filter on the search button.


An additional description for the eql statement filter.


The descriptive text that should be shown when hovering over the "basic" mode button when the query is not convertible to basic mode.[filterNotSelected]

Label for the empty option in a filter control.[empty]

Label for the empty option of an entity property.[true]

Label that should be used instead of true for a boolean property.[false]

Label that should be used instead of false for a boolean property.[notSet]

Label that should be used for the null option in a filter control.

Entity codes are camel cased, eg. CarBrand would become carBrand


Every code requested results in several codes being tried with a number of prefixes: The following prefixes are tried in oder:

  1. (If association view) ModuleName.entities.sourceEntityName.associations[associationName]

  2. ModuleName.entities.entityName

  3. EntityModule.entities.entityName

  4. EntityModule.entities

When rendering a view, the default prefix will be appended with a view type prefix as well. Usually of the form views[viewType].

Example lookup of property "name" on the default list view for entity "user":

  1. MyModule.entities.user.views[listView]


  3. MyModule.entities.views[listView]


  5. EntityModule.entities.views[listView]


TIP: To get a better insight in the message codes generated, use the entity browser in the developer tools.

Message code parameters

Some message codes support parameters, if so, the following could be available:

  • {0}: entity name

  • {1}: entity name inline

  • {2}: label of the entity being modified (if known)

Customizing message code prefixes

The default message code prefix is MODULE_NAME.entities. It’s possible to configure the entity message codes that should be used for a specific module through configuration properties:

        MyModule: prefix to use

You can specify multiple prefixes if you want, just realize this will have a big impact on the number of message codes tried.

Debugging message code lookups

You can trace the message codes being resolved by setting the logger named to TRACE level.

Default message codes

The following is a copy of which contains the default message codes for EntityModule.

EntityModule.adminMenu=Entity management

# Default actions
EntityModule.entities.actions.create=Create a new {1}
EntityModule.entities.actions.view=View {1} details
EntityModule.entities.actions.update=Modify {1}
EntityModule.entities.actions.delete=Delete {1}
EntityModule.entities.actions.cancel=Cancel options

EntityModule.entities.buttons.delete=Delete {1} has been created.{0} has been updated.{0} has been deleted. deleting {1}: {3}. to save, please check the form for one or more errors. went wrong when saving the {1}.  <br />Error code: <strong>{4}</strong> ({3}).

EntityModule.entities.pageTitle.create=Create a new {1}
EntityModule.entities.pageTitle.update=Modify {1}: {2}
EntityModule.entities.pageTitle.view=View {1} details: {2}
EntityModule.entities.pageTitle.delete=Delete {1}: {2}

EntityModule.entities.sortableTable.resultsFound={0,choice, 0#No {2}| 1#1 {1}| 1<{0} {2}} found.
EntityModule.entities.sortableTable.pager=Showing page {0,number,#} of {1,number,#}
EntityModule.entities.sortableTable.pager.nextPage=next page
EntityModule.entities.sortableTable.pager.previousPage=previous page

EntityModule.entities.delete.confirmation=Are you sure you want to delete this {1} and all its associations?
EntityModule.entities.delete.deleteDisabled=Not possible to delete this {1}.
EntityModule.entities.delete.associations=The following items are associated with this {1}:
EntityModule.entities.delete.associatedResults={2} {1}

# Default validation messages

EntityModule.entities.validation.Size=Length should be between {2} and {1} characters.
EntityModule.entities.validation.Length=Length should be between {2} and {1} characters.
EntityModule.entities.validation.NotBlank=A value is required.
EntityModule.entities.validation.NotNull=A value is required.
EntityModule.entities.validation.NotEmpty=A value is required.
EntityModule.entities.validation.Email=Email address is not well-formed.
EntityModule.entities.validation.Min=Value should be greater than or equal to {1}.
EntityModule.entities.validation.Max=Value should be less than or equal to {1}.

EntityModule.entities.validation.alreadyExists=Another entity already has this value.

# Default control messages

3. Default EntityViewProcessors

This chapter lists the general purpose EntityViewProcessor classes that are provided by EntityModule. The default entity views are all built on these processors.

You can use these manually to assemble a DefaultEntityViewFactory, though some will be configured automatically if you use one of the builders.

The class javadoc gives more detailed information. For a full list of all processors available, see the package summary.

Name Purpose


Verifies an the entity configuration or association being requested is not hidden, and a configured AllowableAction is present.


Uses the default repository or query fetcher to fetch all items for the current entity view context.


Registers the default EntityViewCommandValidator and validates the command object if state changing web request (POST, PUT, DELETE, PATCH) is performed.


Fetches items using a configured Function or BiFunction.


Registers a custom EntityPropertyRegistry on the view context.


Adds an EntityQuery language based filter to a list view. Adds both the form with textbox and fetches the items based on the form values.


Renders global feedback on a PageContentStructure (admin page). Global feedback is usually added using EntityViewPageHelper.


Adds a default form at the top of a list view. Optionally add a create button.


Generates the page structure for an entity list view. Add a page title and publishes the EntityPageStructureRenderedEvent.


Configures custom prefixes that should be used for message code resolving.


Creates a Pageable from request parameters and binds it to an EntityViewCommand extension.


Renders a list of properties: allows the properties to be configured as well as the ViewElementMode for rendering.


Creates a form-based layout for an entity view. Supports configuring the form grid (defaults to 2 columns), adding default actions (save/cancel) and adding global binding error messages.


Generates the page structure for a single entity. Adds a page title, builds the entity specific menu (renders it as tabs) and publishes the EntityPageStructureRenderedEvent.


Generates a sortable table for a list of entities. Allows several configuration options like properties to render, sorting options etc.


Configures the name of the template that should be rendered as the result of the controller.


1. Adding a custom property to an entity

To add a custom property to an entity, all we need to do is add the property on the EntityPropertyRegistryBuilder of the entity.

In the following example we will add a property named customProperty of the type String, which always returns myCustomString.

public void configure( EntitiesConfigurationBuilder configuration ) {
                .properties(props ->"customProperty")
                           .valueFetcher(entity -> "myCustomString")


Configuring the custom property to expand it’s search to other properties

To expand the search to other properties, we need to modify the query defined by the custom property. To do this we can add an a custom EntityQueryConditionTranslator under the EntityQueryConditionTranslator.class attribute.

In the following example, we will perform the query of the customProperty on the name and content properties.

public void configure( EntitiesConfigurationBuilder configuration ) {
                .properties(props ->"customProperty")
                           .valueFetcher((entity) -> "myCustomString")
                           .attribute( EntityQueryConditionTranslator.class,
                                                            condition -> {
                                                                EntityQuery entityQuery = new EntityQuery();
                                                                entityQuery.setOperand( EntityQueryOps.OR );
                                                                        new EntityQueryCondition( "name",
                                                                                                  condition.getArguments() ) );
                                                                        new EntityQueryCondition( "content", condition.getOperand(),
                                                                                                  condition.getArguments() ) );
                                                                return entityQuery;
                                                            } )


2. Customizing the listview of an entity

Adding a custom action to each item

In the case where we want to add a custom action to each instance of our entity on a listview, we have to register a viewprocessor (usually an EntityViewProcessorAdapter) on the listview of its entityconfiguration. Then in either the createViewElementBuilders `or `render method, we can edit the configured columns of each instance on the listview using the ViewElementBuilderMap.

From the ViewElementBuilderMap we can retrieve the SortableTableBuilder, registered under SortableTableRenderingViewProcessor.TABLE_BUILDER, on which we can register additional row processors to edit the content of a row. Each row represents a single instance of the entity we are configuring.

To apply changes to the actions of an instance, we will need to find the actions cell in our row, which is registered under EntityListActionsProcessor.CELL_NAME.

In this example, we will add a search button to the actions, which redirects us to google, with the query parameter being the property `name `of the entity instance found on the row.

    protected void createViewElementBuilders(EntityViewRequest entityViewRequest, EntityView entityView, ViewElementBuilderMap builderMap) {
        SortableTableBuilder sortableTableBuilder = builderMap.get(SortableTableRenderingViewProcessor.TABLE_BUILDER, SortableTableBuilder.class);
        sortableTableBuilder.valueRowProcessor((ctx, row) -> {
            ContainerViewElementUtils.find(row, EntityListActionsProcessor.CELL_NAME, TableViewElement.Cell.class).ifPresent(
                    actions -> {
                        MyEntity entity = EntityViewElementUtils.currentEntity(ctx, MyEntity.class);

                        String searchUrl= UriComponentsBuilder.fromUriString("")
                                .queryParam("q", entity.getName())

                                        .iconOnly(new GlyphIcon(GlyphIcon.SEARCH))



3. Creating an extension form

Creating an additional form view for an entity is a very common action. Often you want to manage data that does not actually represent entity type properties. You can attach these to an existing (or separate) form by registering an extension on the EntityViewCommand.

Using extensions has the advantage that data binding and default (annotation based) validation will happen automatically.

If you add an extension to the default entity form (having the SaveEntityViewProcessor) and the extension has errors upon validation, that will count as global validation and will block saving the entity. This makes for a very easy way to extend a default properties form with custom behaviour.

In this how-to we’ll show you how to create a custom view with a form backed by a custom model object registered as an extension. The form will show up as a separate tab on the entities where it’s registered.

Create the extension class

The extension class is the model for our form and will be validated when the form is submitted.

Extension class
static class MyExtension
    private String url;

    private int creationYear;

Create an EntityViewProcessor for the extension

Render the extension on the form and manage the form data. Annotation based validation will be done on form POST.

ExtensionViewProcessorAdapter implementation
 * Custom EntityViewProcessor for MyExtension form.
 * Does not specify an extensionName() but uses the default one (class name),
 * as all processing will be done inside this implementation and there will
 * never be multiple instances of this processor for a single view.
 * Builds a simple form manually.
 * Validation is actually done transparantly using annotation validation.
 * Form elements will show errors because the control names match the extension properties.
static class MyExtensionViewProcessor extends ExtensionViewProcessorAdapter<MyExtension>
    protected MyExtension createExtension( EntityViewRequest entityViewRequest,
                                                EntityViewCommand command,
                                                WebDataBinder dataBinder ) {
        return new MyExtension();

    protected void doPost( MyExtension extension,
                           BindingResult bindingResult,
                           EntityView entityView,
                           EntityViewRequest entityViewRequest ) {
        if ( !bindingResult.hasErrors() ) {
            // Put a dummy feedback message
                                        .text( "Updated url with: " + extension.getUrl() )

    protected void render( MyExtension extension,
                           EntityViewRequest entityViewRequest,
                           EntityView entityView,
                           ContainerViewElementBuilderSupport<?, ?> containerBuilder,
                           ViewElementBuilderMap builderMap,
                           ViewElementBuilderContext builderContext ) {
        builderMap.get( SingleEntityFormViewProcessor.LEFT_COLUMN, ColumnViewElementBuilder.class )
                                  .label( "URL" )
                                  .control( textbox()
                                               .controlName( controlPrefix() + ".url" )
                                               .text( extension.url ) )
                                  .label( "Creation year" )
                                  .control( textbox()
                                               .controlName( controlPrefix() + ".creationYear" )
                                               .text( "" + extension.creationYear ) )

    protected void postRender( MyExtension extension,
                               EntityViewRequest entityViewRequest,
                               EntityView entityView,
                               ContainerViewElement container,
                               ViewElementBuilderContext builderContext ) {
        EntityViewContext entityViewContext = entityViewRequest.getEntityViewContext();

        // Manually change the cancel button url
        container.find( "btn-cancel", ButtonViewElement.class )
                 .ifPresent( button -> button.setUrl(
                   entityViewContext.getLinkBuilder().update( entityViewContext.getEntity() )
                 ) );

Register the view with our processor

The view itself can be registered under any name on the entity configuration. The view name will be used in the message code resolving.

When registering the view, some of the EntityViewCustomizers are used to specify an admin menu item (tab) should be rendered for this view.

Register the view
// Use a configuration template for a simple extension form
// Configure the view to create a menu item under the advanced options
entities.withType( ... )
                    .adminMenu( "/advanced-options/extension" )
                    .andThen( EntityViewCustomizers.formSettings().forExtension( true ) )
                    .andThen( builder -> builder.viewProcessor( new MyExtensionViewProcessor() ) )

Translate the menu item title

Set the right message code for the specific view menu item.

# Default value for every entity with that view
EntityModule.entities.adminMenu.views[extension]=My extension

# Specific title for the menu item on myEntity page
MyModule.entities.myEntity.adminMenu.views[extension]=Extra Fields