Dynamic Attributes in Search
Overview
The Search add-on supports indexing of dynamic attributes of entities.
Dynamic attributes allow extending the data model without changing the database schema, and their values can participate in full-text search alongside static attributes.
The Dynamic Attributes add-on must be present in the project.
Indexing Dynamic Attributes
@DynamicAttributes Annotation
Add the @DynamicAttributes annotation to any method in the index definition interface. In its basic form, it includes all dynamic attributes of the entity in the index without any exclusions:
@JmixEntitySearchIndex(entity = Customer.class)
public interface CustomerIndexDefinition {
@AutoMappedField(includeProperties = {"firstName", "lastName"}) (1)
@DynamicAttributes (2)
void mapping();
}
| 1 | Indexing static attributes of the entity. |
| 2 | Indexing all dynamic attributes of the entity. |
The @DynamicAttributes annotation is repeatable — multiple annotations can be placed on the same method, or spread across different methods:
@JmixEntitySearchIndex(entity = Customer.class)
public interface CustomerIndexDefinition {
@AutoMappedField(includeProperties = {"firstName", "lastName"})
void mapping();
@DynamicAttributes(excludeCategories = {"Internal"})
@DynamicAttributes(excludeAttributes = {"*secret*"}, analyzer = "english")
void dynamicMapping();
}
|
When using multiple |
Annotation Parameters
-
excludeCategories— an array of dynamic attribute category names to exclude from indexing. Wildcard*patterns are supported.The category name is the value of the Name field defined when creating a category in Administration > Dynamic attributes.
@DynamicAttributes(excludeCategories = {"Internal", "Archive"}) void mapping();A pattern cannot consist solely of
*characters — for example,*or**will be rejected. Multiple*characters combined with other text are allowed:*abc,abc*,a*b*c,a**b. -
excludeAttributes— an array of dynamic attribute codes to exclude from indexing. Wildcard*patterns are supported.The attribute code is the value of the System code field defined when creating a dynamic attribute in Administration → Dynamic attributes. It is specified without the
+prefix (the prefix is used only internally within the Jmix metamodel).@DynamicAttributes(excludeAttributes = {"*String*", "dynamicEnumAttr*", "dynamicAttribute"}) void mapping();The
.and+characters are forbidden in attribute codes. A pattern cannot consist solely of*characters — for example,*or**will be rejected. Multiple*characters combined with other text are allowed:*String*,attr*Code*,a**b. -
referenceAttributesIndexingMode— the indexing mode for dynamic attributes of typeEntity(references to other entities). Allowed values:Value Description INSTANCE_NAME_ONLY(default)Only the instance name of the referenced entity is indexed. Individual static or dynamic attributes of the referenced entity are not traversed or indexed.
NONEReference dynamic attributes are not indexed at all.
@DynamicAttributes(referenceAttributesIndexingMode = ReferenceAttributesIndexingMode.NONE) void mapping(); -
analyzer— the name of the Elasticsearch/OpenSearch analyzer to apply to dynamic attribute fields. The analyzer is applied to all supported attribute types, since all of them are indexed astextfields:STRINGattributes,ENUMERATIONattributes, and the instance name (_instance_name) ofENTITY-type attributes.An analyzer tokenizes text during indexing and search: it splits text into words, lowercases tokens, removes stop words, etc.
By default, the
standardanalyzer is used. It splits text at Unicode word boundaries and lowercases all tokens. It works well for most Western European languages but does not handle morphology — for example, it cannot stem words to their root form. For morphologically rich languages such as Finnish, use a language-specific analyzer.@DynamicAttributes(analyzer = "finnish") void mapping();
Combining with Other Annotations
@DynamicAttributes can be combined with @AutoMappedField on the same method:
@JmixEntitySearchIndex(entity = Order.class)
public interface OrderIndexDefinition {
@AutoMappedField(includeProperties = {"number", "product", "customer.lastName"})
@DynamicAttributes(
excludeCategories = {"Internal"},
referenceAttributesIndexingMode = ReferenceAttributesIndexingMode.NONE,
analyzer = "english"
)
void mapping();
}
Programmatic Index Definition
When the programmatic mapping approach via @ManualMappingDefinition is used, DynamicAttributesGroupConfiguration is used for dynamic attributes.
A dynamic attributes group is a set of indexing settings (analyzer, reference handling mode, category and attribute exclusions) applied to a subset of the entity’s dynamic attributes. Each call to addDynamicAttributesGroup() defines one group. If different attributes need to be indexed with different settings, multiple groups can be declared.
|
Since groups are defined through exclusions, the same attribute can easily end up in more than one group. If this happens and both groups have the same Conflicted mapping fields: '+attrCode' and '+attrCode'. Specify the different values of order for them. The default To resolve a conflict, assign different
It is recommended to design groups so that each attribute explicitly falls into only one of them — using |
Minimal Configuration
@JmixEntitySearchIndex(entity = Customer.class)
public interface CustomerIndexDefinition {
@ManualMappingDefinition
default MappingDefinition mapping() {
return MappingDefinition.builder()
.addStaticAttributesGroup(
StaticAttributesGroupConfiguration.builder()
.includeProperties("*")
.withFieldMappingStrategyClass(AutoMappingStrategy.class)
.build()
)
.addDynamicAttributesGroup( (1)
DynamicAttributesGroupConfiguration.builder()
.withFieldMappingStrategyClass(AutoMappingStrategy.class)
.build()
)
.build();
}
}
| 1 | Adding a dynamic attributes group with no additional restrictions. |
Indexing Reference Attributes by Instance Name
By default, dynamic reference attributes are indexed in INSTANCE_NAME_ONLY mode — only the instance name of the referenced entity is included in the index:
@JmixEntitySearchIndex(entity = Customer.class)
public interface CustomerIndexDefinition {
@ManualMappingDefinition
default MappingDefinition mapping() {
return MappingDefinition.builder()
.addStaticAttributesGroup(
StaticAttributesGroupConfiguration.builder()
.includeProperties("*")
.withFieldMappingStrategyClass(AutoMappingStrategy.class)
.build()
)
.addDynamicAttributesGroup(
DynamicAttributesGroupConfiguration.builder()
.withReferenceAttributesIndexingMode( (1)
ReferenceAttributesIndexingMode.INSTANCE_NAME_ONLY)
.withFieldMappingStrategyClass(AutoMappingStrategy.class)
.build()
)
.build();
}
}
| 1 | The default mode; can be omitted. Only the _instance_name of the referenced entity is indexed — its own attributes are not indexed. |
Excluding Reference Attributes from the Index
If reference attributes should not be indexed, use NONE:
@JmixEntitySearchIndex(entity = Customer.class)
public interface CustomerIndexDefinition {
@ManualMappingDefinition
default MappingDefinition mapping() {
return MappingDefinition.builder()
.addStaticAttributesGroup(
StaticAttributesGroupConfiguration.builder()
.includeProperties("*")
.withFieldMappingStrategyClass(AutoMappingStrategy.class)
.build()
)
.addDynamicAttributesGroup(
DynamicAttributesGroupConfiguration.builder()
.withReferenceAttributesIndexingMode( (1)
ReferenceAttributesIndexingMode.NONE)
.withFieldMappingStrategyClass(AutoMappingStrategy.class)
.build()
)
.build();
}
}
| 1 | Reference dynamic attributes are completely excluded from the index. Only STRING and ENUMERATION attributes are indexed. |
Multiple Groups with Different Settings
Multiple dynamic attribute groups can be added, each with its own rules. This is useful when some attributes need one analyzer and others need a different reference indexing mode:
@JmixEntitySearchIndex(entity = Customer.class)
public interface CustomerIndexDefinition {
@ManualMappingDefinition
default MappingDefinition mapping() {
return MappingDefinition.builder()
.addStaticAttributesGroup(
StaticAttributesGroupConfiguration.builder()
.includeProperties("*")
.withFieldMappingStrategyClass(AutoMappingStrategy.class)
.build()
)
.addDynamicAttributesGroup( (1)
DynamicAttributesGroupConfiguration.builder()
.excludeProperties("private*")
.withReferenceAttributesIndexingMode(
ReferenceAttributesIndexingMode.INSTANCE_NAME_ONLY)
.addParameter("analyzer", "english")
.withFieldMappingStrategyClass(AutoMappingStrategy.class)
.build()
)
.addDynamicAttributesGroup( (2)
DynamicAttributesGroupConfiguration.builder()
.excludeCategories("Internal")
.withReferenceAttributesIndexingMode(
ReferenceAttributesIndexingMode.NONE)
.addParameter("analyzer", "english")
.withFieldMappingStrategyClass(AutoMappingStrategy.class)
.build()
)
.build();
}
}
| 1 | First group: attributes with code matching private* are excluded, reference attributes are indexed by instance name, the english analyzer is applied. |
| 2 | Second group: the Internal category is excluded, reference attributes are not indexed, the english analyzer is applied. |
Combining Category and Attribute Exclusions
@JmixEntitySearchIndex(entity = Customer.class)
public interface CustomerIndexDefinition {
@ManualMappingDefinition
default MappingDefinition mapping() {
return MappingDefinition.builder()
.addStaticAttributesGroup(
StaticAttributesGroupConfiguration.builder()
.includeProperties("*")
.withFieldMappingStrategyClass(AutoMappingStrategy.class)
.build()
)
.addDynamicAttributesGroup(
DynamicAttributesGroupConfiguration.builder()
.excludeCategories("Internal", "Archive") (1)
.excludeProperties("*secret*", "attr4") (2)
.withReferenceAttributesIndexingMode(
ReferenceAttributesIndexingMode.NONE)
.addParameter("analyzer", "english") (3)
.withFieldMappingStrategyClass(AutoMappingStrategy.class)
.build()
)
.build();
}
}
| 1 | Excluding categories by name. The . and + characters are not restricted for category names; a standalone * is not allowed. |
| 2 | Excluding attributes by code with wildcard support. The . and + characters are forbidden; a standalone * is not allowed. |
| 3 | The analyzer is applied to all text fields of dynamic attributes: STRING, ENUMERATION, and _instance_name of ENTITY-type attributes. |
|
All limitations described in the Limitations section apply to programmatic configuration as well: supported attribute types, reference indexing rules, attribute chain restrictions, and composite key constraints. |
DynamicAttributesGroupConfiguration Builder Methods
| Method | Description |
|---|---|
|
Excludes dynamic attribute categories by name. A pattern cannot consist solely of |
|
Excludes dynamic attributes by code. The |
|
Sets the indexing mode for reference dynamic attributes: |
|
Sets the field mapping strategy class. |
|
Sets a field mapping strategy instance (takes precedence over the class). |
|
Adds an arbitrary configuration parameter, for example, |
|
Sets a custom property value extractor. |
|
Sets the priority of the group. Used to resolve conflicts when the same attribute falls into multiple groups: the group with the higher value wins. If not set explicitly, the value is taken from the field mapping strategy (for example, |
Change Tracking
The Search add-on automatically tracks changes to dynamic attributes of indexed entities. When a dynamic attribute value changes, the entity is added to the indexing queue and re-indexed during the next queue processing.
This behavior requires no additional configuration — it is enabled automatically when dynamic attributes are present in the index definition.
|
If a dynamic reference attribute value changes on the referenced entity instance, the owning entity instance is also automatically re-indexed. |
Limitations
Supported Dynamic Attribute Types
Only the following dynamic attribute types are indexed:
| Type | Description |
|---|---|
|
String attributes. |
|
Enumeration attributes. Localized values for all available locales are indexed. |
|
Reference attributes (a link to another entity). Behavior is controlled by the |
Attributes of other types (INTEGER, DOUBLE, DECIMAL, DATE, DATE_WITHOUT_TIME, BOOLEAN) are not indexed and are silently ignored.
Reference Attribute Indexing Limitations
The following limitations apply to dynamic attributes of type ENTITY:
-
With
INSTANCE_NAME_ONLYmode, only the instance name of the referenced entity is indexed. Individual static or dynamic attributes of the referenced entity are not included in the index. -
INSTANCE_NAME_ONLYis the only mode in which reference attributes are indexed. Deeper indexing through a dynamic reference is not supported. -
Entities referenced via dynamic attributes do not support composite primary keys.
Only two modes are available due to a framework limitation. Jmix does not support FetchPlan for dynamic attributes: when loading an entity instance, only the LOAD_DYN_ATTR hint is available, which loads all dynamic attributes in full. Dynamic properties are intentionally excluded from FetchPlan and loaded separately via this hint. Since there is no way to specify which fields of the referenced entity instance should be loaded when traversing a dynamic reference attribute, the instance name — computed uniformly for any entity — is the only practically feasible indexing option.
Attribute Chain Limitations
-
Dynamic attributes via static references — dynamic attributes of entity instances linked to the root entity through a static reference attribute (for example,
customer.+dynamicAttr) are not indexed. Only dynamic attributes directly on the root entity instance are indexed. -
Static attributes via dynamic references — static attributes of an entity instance referenced by an
ENTITY-type dynamic attribute are not indexed. Only the instance name of that entity instance is available.
Other Limitations
-
The
.(dot) and+(plus) characters are forbidden in attribute codes (excludeAttributes). There are no such restrictions for category names (excludeCategories). -
A pattern cannot consist solely of
*characters — for example,*or**will be rejected. Multiple*characters combined with other text are allowed:*abc,abc*,a*b*c,a**b. -
The Dynamic Attributes add-on (
jmix-dynattr) must be present in the project.