Advanced — New in 1.2.0
XML Rule Definitions
rulii-spring 1.2.0 introduces an XML namespace that lets you declare rules, rulesets, and validation rules entirely in XML files. This is useful when business logic needs to live outside of compiled code — in configuration files that non-developers can edit and deploy without recompiling.
Namespace Declaration
Add the rulii namespace to your Spring XML context file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:r="http://www.rulii.org/schema/rulii"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.rulii.org/schema/rulii
classpath:org/rulii/spring/xml/rulii-spring.xsd">
<!-- rules go here -->
</beans>
Setting the Default Language
All expression elements — given, then, otherwise, pre-condition, initializer, stop-condition, finalizer — are evaluated by a script engine. The default language is "el" (SpEL) when none is configured. Use <r:scripting> to change the file-wide default:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:r="http://www.rulii.org/schema/rulii"
...>
<!-- Use SpEL (the default) -- expressions reference bindings via #ctx -->
<r:scripting defaultLanguage="el"/>
<r:rule name="PremiumOrderRule">
<given>#ctx.orderTotal >= 500</given>
<then>#ctx.discountTier = 'PREMIUM'</then>
</r:rule>
</beans>
Place <r:scripting> once near the top of the file, before any <r:rule> or <r:ruleset> elements — the setting applies to every expression element that follows it. Any individual element can still override the language with a language attribute:
<!-- File default is SpEL, but this one condition uses GraalJS -->
<r:scripting defaultLanguage="el"/>
<r:rule name="RegexRule">
<given language="js">/^[A-Z]{3}-\d{4}$/.test(ctx.productCode)</given>
<then>#ctx.validated = true</then>
</r:rule>
Declaring a Rule
Use <r:rule> to define a rule with a condition (pre-condition, given), actions (then, otherwise), all expressed as SpEL:
<r:rule name="AgeVerificationRule">
<given>#ctx.age >= 18</given>
<then>#ctx.status = 'approved'</then>
<otherwise>#ctx.status = 'denied'</otherwise>
</r:rule>
Declaring a RuleSet
<r:ruleset> supports typed parameters, lifecycle hooks, an inline rule collection, and both built-in and custom validation rules:
<r:ruleset name="RegistrationValidation">
<r:param name="name" type="java.lang.String"/>
<r:param name="email" type="java.lang.String"/>
<r:param name="age" type="java.lang.Integer">
<r:default-value>0</r:default-value>
</r:param>
<r:rules>
<r:notNull binding="name" errorCode="NAME_REQUIRED"/>
<r:notBlank binding="name" errorCode="NAME_BLANK"/>
<r:size binding="name" min="2" max="50"/>
<r:email binding="email" errorCode="EMAIL_INVALID"/>
<r:min binding="age" min="18" errorCode="UNDERAGE"/>
</r:rules>
</r:ruleset>
Lifecycle Hooks
RuleSets support four lifecycle hooks, each taking a SpEL expression:
<r:ruleset name="PricingRules">
<pre-condition>#ctx.enabled == true</pre-condition>
<initializer>#ctx.discount = 0</initializer>
<stop-condition>#ctx.discount >= #ctx.maxDiscount</stop-condition>
<finalizer>#ctx.totalPrice = #ctx.basePrice - #ctx.discount</finalizer>
<r:rules>
<r:rule name="LoyaltyDiscount">
<given>#ctx.loyaltyYears >= 5</given>
<then>#ctx.discount = #ctx.discount + 10</then>
</r:rule>
</r:rules>
</r:ruleset>
Referencing Existing Beans or Classes
You can include existing Spring beans or annotated rule classes inside a ruleset's rule collection:
<r:rules>
<!-- Reference an existing Spring bean -->
<r:bean-ref name="myEligibilityRule"/>
<!-- Reference an annotated @Rule class -->
<r:class-ref class="com.example.rules.AgeVerificationRule"/>
</r:rules>
Predefined Validation Rule Elements
All 38 built-in validators are available as first-class XML elements. Every element accepts binding (required), errorCode, severity, and errorMessage. Error messages support Spring property placeholder substitution:
<r:notNull binding="name" errorMessage="${validation.name.required}"/>
<r:notBlank binding="name"/>
<r:size binding="name" min="2" max="100"/>
<r:email binding="email"/>
<r:pattern binding="phone" regexp="^\+?[0-9]{7,15}$"/>
<r:min binding="age" min="18"/>
<r:max binding="quantity" max="999"/>
<r:future binding="appointmentDate"/>
<r:positive binding="price" severity="FATAL"/>
<r:in binding="status" values="PENDING,ACTIVE,CLOSED"/>
<r:startsWith binding="code" values="PRD-,SKU-"/>
Custom Scripted Validation Rules
When the predefined validators don't cover a business case, <r:validationRule> lets you write the condition as a script expression. The rule registers a RuleViolation when the <given> condition returns false:
<r:validationRule name="ValidShippingDate"
errorCode="SHIPPING_DATE_INVALID"
severity="ERROR"
errorMessage="Shipping date must be today or in the future">
<given>#ctx.shippingDate != null and !#ctx.shippingDate.isBefore(T(java.time.LocalDate).now())</given>
</r:validationRule>
Supported attributes:
| Attribute | Required | Description |
|---|---|---|
| name | Yes | Unique rule identifier, also used as the Spring bean name |
| errorCode | Yes | Code placed in the RuleViolation when the condition fails |
| severity | No | ERROR (default), FATAL, WARNING, INFO |
| errorMessage | No | Message template shown in the violation (supports ${...} placeholders) |
| defaultMessage | No | Fallback message when the template cannot be resolved via MessageResolver |
| description | No | Human-readable documentation for the rule |
The <given> child element supports the same language attribute as any other expression element, letting you mix languages within the same file. Custom validation rules can also be placed directly inside a <r:ruleset>:
<r:ruleset name="OrderValidation" validating="true">
<r:param name="productCode" type="java.lang.String"/>
<r:param name="quantity" type="java.lang.Integer"/>
<r:param name="shipDate" type="java.time.LocalDate"/>
<r:rules>
<!-- Predefined rules -->
<r:notBlank binding="productCode" errorCode="ORD-001"/>
<r:min binding="quantity" errorCode="ORD-002" min="1"/>
<!-- Custom scripted validation rule -->
<r:validationRule name="FutureShipDate"
errorCode="ORD-003"
errorMessage="Shipping date must not be in the past">
<given>#ctx.shipDate != null and !#ctx.shipDate.isBefore(T(java.time.LocalDate).now())</given>
</r:validationRule>
</r:rules>
</r:ruleset>
Loading XML Files via @RuleScan
Use the xmlLocations attribute on @RuleScan to tell rulii-spring where to find your XML rule files. All *.xml files found inside each folder are loaded automatically. See Rule Scanning & Discovery for the full details.
@SpringBootApplication
@RuleScan(
scanBasePackages = "com.example.rules",
xmlLocations = {
"classpath:rules/pricing/",
"classpath:rules/validation/"
}
)
public class MyApp { }
Registration order
Class-based rules from scanBasePackages are always registered first. XML rules are loaded afterwards, in the order their folders are listed in xmlLocations.