03 — Getting Started
Rule Scanning & Discovery
rulii-spring automatically discovers your @Rule classes and registers them as Spring beans. There are two ways this happens: explicit scanning with @RuleScan, or automatic fallback scanning.
@RuleScan (Recommended)
Place @RuleScan on your main application class or any @Configuration class. It accepts an array of base packages to scan.
@SpringBootApplication
@RuleScan(scanBasePackages = {"com.example.rules", "com.example.validation"})
public class MyApp { }
If you omit scanBasePackages, the scanner defaults to the package of the annotated class:
// Scans com.example and all sub-packages
package com.example;
@SpringBootApplication
@RuleScan
public class MyApp { }
How Scanning Works
Under the hood, @RuleScan triggers a RuleRegistrar (an ImportBeanDefinitionRegistrar). Here's the flow:
RuleRegistrarreads thescanBasePackagesattribute from@RuleScan- A
RuleBeanDefinitionScanner(extendingClassPathBeanDefinitionScanner) scans those packages for classes annotated with@Rule - Each discovered class is wrapped in a
BeanDefinitionthat usesRuleBeanBuilder.build()as its factory method - The bean is registered under the rule's name (derived from the class by
ClassBasedRuleBuilder.getRuleName()) - A
RuleRegistrarMetaInforecord is stored, tracking the scanned packages and rule count
Automatic Fallback Scanning
If you don't use @RuleScan, rulii-spring still tries to find your rules. The auto-configured RuleBeanDefinitionRegistryPostProcessor kicks in and uses Spring Boot's AutoConfigurationPackages to determine base packages.
This works, but you'll see a log message recommending explicit @RuleScan usage. The fallback exists for convenience; explicit scanning gives you full control over which packages are included.
Tip
Always prefer explicit @RuleScan in production applications. It's faster (scans only what you specify) and makes your configuration self-documenting.
What Gets Scanned
The scanner looks exclusively for classes annotated with rulii's @Rule annotation. A typical rule class looks like this:
package com.example.rules;
@Rule
public class AgeVerificationRule {
@Given
public boolean isAdult(Integer age) {
return age != null && age >= 18;
}
@Then
public void allow() {
// approved
}
@Otherwise
public void deny() {
// rejected
}
}
After scanning, this rule is available as a Spring bean named "AgeVerificationRule" (the name is derived from the class). You can look it up through the RuleRegistry or inject it directly.
Multiple @RuleScan Locations
You can specify multiple packages in a single @RuleScan annotation. All packages and their sub-packages are scanned recursively.
@RuleScan(scanBasePackages = {
"com.example.rules.eligibility",
"com.example.rules.pricing",
"com.example.rules.compliance"
})
Loading XML Rule Files — xmlLocations
Since 1.2.0, @RuleScan also accepts an xmlLocations attribute. Pass one or more classpath folder paths; all *.xml files found inside each folder are loaded via XmlBeanDefinitionReader and their bean definitions are merged into the same application context.
@SpringBootApplication
@RuleScan(
scanBasePackages = "com.example.rules",
xmlLocations = {
"classpath:rules/pricing/",
"classpath:rules/validation/"
}
)
public class MyApp { }
You can use xmlLocations alone (without scanBasePackages) if all your rules live in XML:
@RuleScan(xmlLocations = "classpath:rules/")
Registration order
Class-based rules from scanBasePackages are always registered first. XML rules are loaded afterwards, preserving a predictable registration order. This matters if your rulesets reference other rules by name.
For details on writing XML rule files, see XML Rule Definitions and XML RuleFlows.
XML File & Line Provenance (new in 2.1.0)
Bean definitions registered from @RuleScan(xmlLocations) files carry an XmlSource — the Resource plus the 1-based line of the element's start tag — as their BeanDefinition.getSource(). XML-built rules, rulesets, and ruleflows pass it through to their rulii SourceDefinition, so getDefinition().getSource() reports the file and line the artifact was declared at, instead of a framework stack frame:
Rule rule = ruleRegistry.get("LoyaltyDiscount");
rule.getDefinition().getSource();
// reports classpath:rules/order/pricing.xml and the declaring line
Location labels are stable across machines: for folder scans the label is the folder plus file name as declared (classpath:rules/order/pricing.xml), not the absolute file URL the scan resolved. XML loaded another way (@ImportResource, <import>) keeps the file but has no line.
The injectable RuleRegistrarMetaInfo record also gains xmlFiles(): the XML files the declared locations resolved to and that were loaded, in load order, using the same location labels.