OCL Constraints
The Object Constraint Language (OCL) is the OMG-standard expression language for adding invariants, pre-conditions, and post-conditions to UML models. kUML implements a practical subset suitable for invariants and state-machine guards.
Invariants on classifiers
Add constraint(name, body) inside any classifier:
classOf(name = "Order") {
attribute(name = "total", type = "BigDecimal")
attribute(name = "discount", type = "BigDecimal")
constraint(name = "NonNegativeTotal",
body = "self.total >= 0")
constraint(name = "DiscountWithinLimits",
body = "self.discount >= 0 and self.discount <= self.total")
}
The body is plain OCL. self always refers to the surrounding classifier instance.
Validation
kuml validate order.kuml.kts
The CLI parses every constraint, evaluates it, and exits with code:
-
0if every constraint holds. -
1if any constraint is violated. Each violation prints the constraint name, the classifier it sits on, and the OCL expression. -
non-zero if a constraint fails to parse (syntax error).
In a Gradle build, the kumlValidate task runs the same check and fails the build if
failOnValidationViolations is true (the default).
Supported OCL subset (v0.3.0)
| Category | Supported |
|---|---|
Boolean operators |
|
Comparison |
|
Arithmetic |
|
Literals |
Integer ( |
Navigation |
|
Collection ops |
|
Conditionals |
|
State-machine guards
Transition guards on state machines use the same OCL subset, with two additional
implicit variables: event (the triggering event with its payload as a Map<String, Any?>)
and the model instance binding.
stateMachine(name = "Cart") {
initial("empty")
state("active") {
// Read attribute from current variables map
constraint(name = "PositiveItemCount", body = "self.itemCount > 0")
}
state("checked_out") { final() }
transition(from = "active", to = "checked_out",
trigger = "checkout",
guard = "event.totalCents > 0 and self.itemCount > 0")
}
The runtime parses the guard at evaluation time, navigates event.totalCents against the
event’s payload map, and self.itemCount against the machine’s variables map. Both are
populated by the simulator’s event stream — see simulate.
Profile-level constraints
Stereotypes can carry constraints that apply automatically to any element with that stereotype:
profile(name = "JavaEE") {
stereotype(name = "Entity", extending = UmlMetaclass.Class) {
constraint(name = "RequiresIdAttribute",
body = "self.ownedAttribute->exists(a | a.appliedStereotype.name = 'Id')")
}
}
When you applyProfile("JavaEE") and stereotype a class as Entity, the validator
checks the constraint without you adding it manually.
What’s not supported
-
let … in …— declarative bindings (V2) -
oclIsKindOf,oclAsType— type tests and casts (V2) -
Multi-argument operation calls in expressions (V2)
-
User-defined OCL helper operations (V2)
If you hit a limit, fall back to a smaller constraint or split it into multiple named constraints — the validator runs them independently anyway.