| name | new-validation-rule |
| description | Guide for creating a new custom ValidationRule in rulii — rule class, builder class, annotations, supported types, isValid logic, and customizeViolation |
| user-invocable | true |
Creating a New Validation Rule in rulii
Use this guide when the user asks to create a custom validation rule that integrates with the ValueValidationRule framework (writes violations to RuleViolations, supports type checking, severity, error codes).
This guide covers creating the rule and its builder only. It does NOT add it to Validators.
1. File structure
Create two files in a new sub-package under org.rulii.validation.rules.<rulename>/:
src/main/java/org/rulii/validation/rules/
mycheck/
MyCheckValidationRule.java ← the rule
MyCheckValidationRuleBuilder.java ← the builder
2. The rule class
package org.rulii.validation.rules.mycheck;
import org.rulii.annotation.Description;
import org.rulii.annotation.Rule;
import org.rulii.context.RuleContext;
import org.rulii.model.function.Function;
import org.rulii.validation.Severity;
import org.rulii.validation.ValueValidationRule;
import java.util.List;
@Rule
@Description("Value must satisfy MyCheck.")
public class MyCheckValidationRule extends ValueValidationRule {
public static final List<Class<?>> SUPPORTED_TYPES = List.of(String.class);
public static final String ERROR_CODE = "myCheckValidationRule.errorCode";
public static final String DEFAULT_MESSAGE = "Value {0} failed MyCheck.";
public static MyCheckValidationRuleBuilder builder(Function<?> function) {
return new MyCheckValidationRuleBuilder(function);
}
MyCheckValidationRule(Function<?> valueFunction, String errorCode, Severity severity,
String errorMessage, String valueName) {
super(valueFunction, errorCode, severity, errorMessage, DEFAULT_MESSAGE, valueName);
}
@Override
protected boolean isValid(RuleContext ruleContext, Object value) {
if (value == null) return false;
String s = (String) value;
return s.startsWith("MY_");
}
@Override
public List<Class<?>> getSupportedTypes() {
return SUPPORTED_TYPES;
}
@Override
public String toString() {
return "MyCheckValidationRule";
}
}
Key points
@Rule and @Description are required annotations.
- Constructor is package-private — callers use
builder().
isValid() is only called when checkType() passes, so value will be an instance of one of SUPPORTED_TYPES. However, it may still be null (null passes checkType()).
- Wrong-type values →
checkType() returns false → SKIP (no violation, no isValid() call).
3. The builder class
package org.rulii.validation.rules.mycheck;
import org.rulii.model.function.Function;
import org.rulii.validation.Severity;
import org.rulii.validation.ValueValidationRuleBuilder;
public class MyCheckValidationRuleBuilder
extends ValueValidationRuleBuilder<MyCheckValidationRuleBuilder, MyCheckValidationRule> {
public MyCheckValidationRuleBuilder(Function<?> valueFunction) {
super(valueFunction);
errorCode(MyCheckValidationRule.ERROR_CODE);
severity(Severity.ERROR);
message(MyCheckValidationRule.DEFAULT_MESSAGE);
}
@Override
protected MyCheckValidationRule createValueValidationRule() {
return new MyCheckValidationRule(
getValueFunction(),
getErrorCode(),
getSeverity(),
getErrorMessage(),
getValueName()
);
}
}
Builder API (inherited from ValueValidationRuleBuilder)
MyCheckValidationRule.builder(fn)
.errorCode("custom.code") // override default error code
.severity(Severity.WARNING) // default is ERROR
.message("Custom message") // override default message
.valueName("fieldLabel") // name used in RuleViolation params (defaults to binding name)
.name("CustomRuleName") // override the rule's name
.description("...")
.build() // returns Rule (wraps the ValidationRule)
4. Usage
import static org.rulii.validation.rules.Validators.binding;
import static org.rulii.validation.rules.Validators.value;
Rule rule = MyCheckValidationRule.builder(binding("myField")).build();
Rule rule = MyCheckValidationRule.builder(value(someObject)).build();
Rule rule = MyCheckValidationRule.builder(binding("code"))
.message("Code must start with MY_")
.severity(Severity.WARNING)
.build();
RuleSet<?> ruleSet = RuleSet.builder()
.with("MyValidation")
.rule(MyCheckValidationRule.builder(binding("code")).build())
.validating()
.build();
5. Rules with extra constructor parameters
If the rule needs configuration beyond the standard fields (e.g. a min length), add them to the constructor and builder:
MyCheckValidationRule(Function<?> valueFunction, String errorCode, Severity severity,
String errorMessage, String valueName, int minLength) {
super(valueFunction, errorCode, severity, errorMessage, DEFAULT_MESSAGE, valueName);
this.minLength = minLength;
}
public class MyCheckValidationRuleBuilder
extends ValueValidationRuleBuilder<MyCheckValidationRuleBuilder, MyCheckValidationRule> {
private int minLength;
public MyCheckValidationRuleBuilder(Function<?> valueFunction, int minLength) {
super(valueFunction);
this.minLength = minLength;
errorCode(MyCheckValidationRule.ERROR_CODE);
severity(Severity.ERROR);
message(MyCheckValidationRule.DEFAULT_MESSAGE);
}
@Override
protected MyCheckValidationRule createValueValidationRule() {
return new MyCheckValidationRule(
getValueFunction(), getErrorCode(), getSeverity(),
getErrorMessage(), getValueName(), minLength);
}
}
public static MyCheckValidationRuleBuilder builder(Function<?> function, int minLength) {
return new MyCheckValidationRuleBuilder(function, minLength);
}
6. Customising the violation (extra params in violation message)
Override customizeViolation() to add extra parameters that can be referenced in the message template:
@Override
protected void customizeViolation(RuleContext ruleContext, RuleViolationBuilder builder) {
builder.param("minLength", this.minLength);
}
7. Supported types — common patterns
public static final List<Class<?>> SUPPORTED_TYPES = List.of(Object.class);
public static final List<Class<?>> SUPPORTED_TYPES = List.of(CharSequence.class);
public static final List<Class<?>> SUPPORTED_TYPES = List.of(Number.class);
public static final List<Class<?>> SUPPORTED_TYPES = List.of(Collection.class);
public static final List<Class<?>> SUPPORTED_TYPES = List.of(String.class, CharSequence.class);
Type matching uses Class.isAssignableFrom() — specifying CharSequence.class will accept String, StringBuilder, etc.
Checklist