3.1 XPath Syntax & Association Traversal
Key Takeaways
- Mendix XPath abstracts relational SQL queries over domain entities and associations, using entity path prefixes (//ModuleName.EntityName) and bracketed predicate constraints ([...]).
- Forward association traversal navigates from an entity across a reference defined on it or pointing from it, structured as [Module.Association_Name/Module.TargetEntity/Attribute = 'value'].
- Reverse association traversal queries parent or referenced entities based on child attributes (e.g., finding Customer where Sales.Order_Customer/Sales.Order/TotalAmount > 500) without data denormalization.
- Multi-hop traversal paths generate chained relational joins across intermediate tables, requiring developers to balance path depth against database query performance.
- Predicate evaluation strictly adheres to boolean operator precedence (not > and > or), making explicit parenthetical grouping essential to avoid logical evaluation traps.
3.1 XPath Syntax & Association Traversal
Intermediate Exam Focus: XPath in Mendix is the primary declarative query language used in database retrieves, entity access rules, and page data source constraints. Intermediate developers must master forward and reverse association navigation, multi-hop path expressions, bracket scoping, and boolean operator precedence. Questions frequently test tricky association directions, the exact placement of entity and association names in path segments, and how parentheses alter boolean logic.
Understanding XPath in the Mendix Architecture
Unlike standard W3C XPath used for navigating XML document trees, Mendix XPath is a specialized domain query language tailored specifically for the Mendix Domain Model. It acts as an Object-Relational Mapping (ORM) abstraction layer over relational database engines (PostgreSQL, Microsoft SQL Server, Oracle, and MySQL).
In Mendix applications, XPath queries appear across three primary runtime architectural contexts:
- Microflow Retrieve Actions: In a Database Retrieve activity, the developer selects an Entity and provides an optional XPath constraint to filter records directly at the database tier before objects are instantiated in Mendix Runtime JVM memory.
- Entity Access Rules (Security): Defined on entities within Module Security, XPath constraints restrict which rows an assigned user role can read or write (row-level data security).
- UI Widget Data Sources: Data Grids, List Views, and Reference Selectors use XPath constraints to filter displayed records dynamically based on the active user session, enclosing page context, or fixed business criteria.
Basic XPath Syntax and Predicate Anatomy
A complete Mendix XPath expression begins with an optional entity declaration followed by one or more predicate filters enclosed in square brackets [...].
//Sales.Order[Status = 'Processing'][TotalAmount >= 150.00]
When entered inside a Microflow Database Retrieve action where the target entity (Sales.Order) is already explicitly selected in the dialog, the leading entity specifier //Sales.Order is implied and omitted, leaving only the predicate expression:
[Status = 'Processing' and TotalAmount >= 150.00]
Path Components and Data Types
Predicates evaluate attributes, associations, and system members against literal values, system tokens, or variables:
- String Literals: Must always be enclosed in single quotes (
'Active'). Double quotes ("Active") are invalid syntax in Mendix XPath and will trigger a modeling consistency error in Studio Pro. - Boolean Literals: Represented as function-style literals
true()andfalse(). Plain unquotedtrueorfalsewill produce a parsing error. - Numeric Literals: Entered as plain integers or decimal numbers without quotes (e.g.,
25,99.95). - Null / Unset Attribute Values: Tested with the keyword
emptyor the keywordNULL— Mendix documents both, they are equivalent, and neither is quoted. Both keywords apply to attributes only: the presence or absence of an association cannot be tested this way (see Trap 4).
// Checking if an association is populated
[Sales.Order_Customer != empty]
// Checking if a string attribute is populated
[CustomerNumber != empty and CustomerNumber != '']
Association Traversal: Forward Navigation
Association traversal allows filtering an entity based on attributes or existence of related entities. In the Mendix Domain Model, an association links a starting entity to a target entity with a defined multiplicity (1-to-1, 1-to-many, or many-to-many).
Forward Path Syntax
A forward traversal moves from the entity being retrieved across an association to inspect the associated entity. The path pattern follows a strict three-part anatomy:
[ModuleName.AssociationName/ModuleName.TargetEntity/TargetAttribute = Value]
Consider a domain model where entity Sales.Order has a 1-to-many reference Sales.Order_Customer pointing to Sales.Customer:
// Retrieving Orders placed by customers located in 'Rotterdam'
[Sales.Order_Customer/Sales.Customer/City = 'Rotterdam']
In this expression:
Sales.Order_Customeris the association name (prefixed by its defining module).Sales.Customeris the target entity name.Cityis the attribute onSales.Customer.
Under the hood, the Mendix Runtime compiles this XPath constraint into an SQL INNER JOIN between the sales$order table and the sales$customer table using the foreign key column.
Association Traversal: Reverse Navigation
Reverse association traversal occurs when you query an entity by navigating backwards across an association owned by or pointing from another entity. In relational database terms, this corresponds to querying a parent or referenced entity based on conditions present in child or referencing records.
Reverse Path Syntax
Suppose you are retrieving Sales.Customer records. The association Sales.Order_Customer is defined between Sales.Order and Sales.Customer. To find all customers who have at least one order exceeding $1,000, you write:
// Starting Entity: Sales.Customer
// Traversal navigates backward through Order_Customer to Order
[Sales.Order_Customer/Sales.Order/TotalAmount > 1000.00]
Notice the symmetry of the syntax:
Sales.Order_Customerrepresents the association.Sales.Orderis the entity at the other end of the association.TotalAmountis the attribute onSales.Order.
This reverse query generates an SQL EXISTS subquery or semi-join, retrieving only customers who have associated order records meeting the threshold. You do not need to create a redundant reverse association or denormalize data.
Multi-Hop Association Traversal
XPath expressions can traverse multiple associations in a single continuous path:
// Starting from OrderLine, traversing to Order, then Customer, then Region
[Sales.OrderLine_Order/Sales.Order/Sales.Order_Customer/Sales.Customer/Sales.Customer_Region/Sales.Region/RegionCode = 'EMEA']
Each additional hop (/Association/Entity/) adds another SQL join to the generated query. While powerful, multi-hop traversals across three or more associations can degrade performance on large tables, especially when applied inside high-frequency microflows or unindexed foreign keys.
Syntax Reference: Association Traversal Patterns
| Traversal Type | Starting Entity | XPath Pattern | SQL Translation Mechanism |
|---|---|---|---|
| Forward Traversal | Sales.Order | [Sales.Order_Customer/Sales.Customer/Tier = 'Gold'] | Inner Join on Customer Foreign Key |
| Reverse Traversal | Sales.Customer | [Sales.Order_Customer/Sales.Order/Status = 'Shipped'] | Semi-Join / EXISTS on Order Table |
| Association Population | Sales.Order | [Sales.Order_Customer != empty] | Foreign Key IS NOT NULL check |
| Association Emptiness | Sales.Customer | [not(Sales.Order_Customer/Sales.Order)] | LEFT JOIN ... WHERE Order.id IS NULL |
| Multi-Hop Traversal | Sales.Invoice | [Sales.Invoice_Order/Sales.Order/Sales.Order_Customer/Sales.Customer/IsActive = true()] | Chained INNER JOIN operations |
Predicate Filtering and Logical Operators
XPath supports three logical boolean operators: and, or, and not. Understanding their execution rules and precedence is a major focus of intermediate exam questions.
Operator Precedence Rules
Mendix evaluates boolean operators in the following strict order of precedence:
not(highest precedence — binds immediately to the expression following it)and(second highest precedence)or(lowest precedence)
Without explicit parentheses, expressions combining and and or can yield completely unexpected result sets:
// AMBIGUOUS / DANGEROUS:
[Status = 'Completed' or Status = 'Pending' and Priority = 'High']
Because and has higher precedence than or, the database interprets this as:
Status = 'Completed' OR (Status = 'Pending' AND Priority = 'High')
All orders with Status = 'Completed' will be returned regardless of their priority! If the business requirement was to retrieve completed or pending orders that are high priority, parentheses are mandatory:
// CORRECT:
[(Status = 'Completed' or Status = 'Pending') and Priority = 'High']
Bracket Scoping: Single Predicate vs Multiple Predicates
Mendix allows stacking multiple bracketed predicates:
// Stacking brackets:
[Status = 'Active'][TotalAmount > 500]
// Single predicate with AND:
[Status = 'Active' and TotalAmount > 500]
These two forms are functionally equivalent; both represent an intersection (logical AND). However, stacking brackets does not support logical OR between the brackets. Any disjunction must be expressed inside a single predicate using the or operator.
Working with the not() Operator
The not operator can negate an attribute comparison or an entire association existence check:
// Negating an attribute comparison
[not(Status = 'Cancelled')]
// Equivalent to:
[Status != 'Cancelled']
// Negating an association check (finding customers with no orders)
[not(Sales.Order_Customer/Sales.Order)]
When negating associations, [not(Association/TargetEntity)] checks for the absence of related records.
Practical Exam Traps & Common Anti-Patterns
Trap 1: Quoting Identifiers or Using Double Quotes for Literals
In Mendix XPath, entity and attribute names must never be quoted. String literals must strictly use single quotes ('value'). Double quotes ("value") or unquoted strings (value) will cause an XPath parsing compilation error.
Trap 2: Inverting Association and Entity Order
A common exam distractor lists path segments out of order:
- Incorrect:
[Sales.Customer/Sales.Order_Customer/City = 'London'] - Correct:
[Sales.Order_Customer/Sales.Customer/City = 'London']The rule is immutable: the association name always precedes the target entity name in the slash-delimited path.
Trap 3: Confusing empty with Empty String
Testing [Notes = ''] only matches records where the string is explicitly set to a zero-length string. It does not match records where Notes has no value at all. To cover both, write:
[Notes = empty or Notes = '']
([Notes = NULL] is the documented synonym of [Notes = empty] — pick one style and stay consistent.)
Trap 4: Using empty to Test an Association
This is the most common XPath mistake at intermediate level. The empty and NULL keywords only work on attributes; Mendix states explicitly that the existence of an association cannot be confirmed this way. To find objects with no associated object, negate the association path with the not() function:
- Wrong:
[Sales.Customer_AccountManager = empty] - Correct:
[not(Sales.Customer_AccountManager/Sales.AccountManager)]
The same shape expresses "has none matching a condition": [not(Sales.Customer_Order/Sales.Order/TotalPrice > 30000)] returns customers who have never placed an order above 30,000 — including customers with no orders at all, which is exactly what [Sales.Customer_Order/Sales.Order/TotalPrice <= 30000] would silently drop.
In an application, the entity Billing.Invoice has a 1-to-many reference Billing.Invoice_Customer pointing to Billing.Customer. A developer needs to retrieve all Billing.Customer objects that have at least one invoice with an AmountDue greater than 500. Which XPath constraint correctly retrieves these customers?
A developer writes the following XPath constraint on the Order entity: [Status = 'Draft' or Status = 'Submitted' and TotalAmount > 1000]. What records will the database return?
Which XPath constraint correctly retrieves all Sales.Customer objects that have no associated Sales.AccountManager across the Sales.Customer_AccountManager association?