- Overview
- Quick Start
- Introduction
- Guides
- Client Libraries
- API Reference
- Examples
- DDD Resources
- Validation user guide
- Validation developer guide
Overview of MessageValidator
Spine Validation enforces most constraints via code generation from .proto options.
Sometimes this is not enough, or not possible.
Use validators when you need custom logic in code:
- validate external message types whose
.protofiles you cannot change (e.g.google.protobuf.Timestamp); - validate local messages when the rule requires computation and cannot be expressed as proto options.
Validators are implemented via io.spine.validation.MessageValidator<M> and executed by
io.spine.validation.ValidatorRegistry.
When to use validators
Prefer .proto options when you can:
- Use built-in options.
- If built-ins are not enough, implement custom validation options.
Use MessageValidator when:
- You cannot modify the
.protosource of a message type (external messages). - You need checks that depend on multiple fields, computations, or library calls (local messages).
Create a validator
To validate a message type M:
- Implement
io.spine.validation.MessageValidator<M>. - Make the validator discoverable via Java
ServiceLoader(recommended), or register it inValidatorRegistryexplicitly at application startup. - Ensure the class has a public no-args constructor (Kotlin/Java default constructors work).
ServiceLoader discovery (recommended)
ValidatorRegistry loads implementations of MessageValidator from the classpath via
ServiceLoader.
On the JVM, the easiest way to generate the required META-INF/services/... entry is to annotate
your validator with @AutoService(MessageValidator::class):
import com.google.auto.service.AutoService
import io.spine.validation.DetectedViolation
import io.spine.validation.MessageValidator
@AutoService(MessageValidator::class)
public class MeetingValidator : MessageValidator<Meeting> {
override fun validate(message: Meeting): List<DetectedViolation> = emptyList()
}
For AutoService to work, you’ll also need to update your build. Please see the documentation of the library for details.
Explicit registration (alternative)
If you prefer not to rely on classpath discovery, add validators during application startup:
ValidatorRegistry.add(Meeting::class, MeetingValidator())
Apply a validator
Validators are executed when the generated validation code is invoked. In practice, this happens in three common ways:
Validate a message directly via the registry:
val violations = ValidatorRegistry.validate(message)Please note that this approach does not apply any checks generated from
.protooptions, only registered validators.Build a local message of the corresponding type.
When you callM.Builder.build(), the generated validation runs forM: it applies checks produced from.protooptions (if any) and executes all validators registered forMviaValidatorRegistry.Validate nested message fields by marking them with
(validate) = true.
When the enclosing message is validated (for example, during the enclosing builder’sbuild()), Spine Validation also validates the nested values of those fields. This nested validation runs both the nested message’s generated constraints (if any) and any validators registered for the nested message type — including external message types.
Multiple validators per message type
You can register more than one validator for the same message type. When the message is validated, Spine Validation applies all registered validators and reports their violations together.