Using ValidatorRegistry

In most cases, you don’t need to interact with io.spine.validation.ValidatorRegistry directly. Spine Validation discovers validators automatically and applies them as part of the generated validation API.

Use ValidatorRegistry directly when you need to:

  • register a validator explicitly, for example, at application startup;
  • inspect which validators are registered for a message type;
  • remove or replace validators, for example, to override ones discovered automatically;
  • validate a message using validators only.

ValidatorRegistry uses Java ServiceLoader to discover validators when the object is first initialized.

Validate a message 

There are two common ways to validate a message when you have validators:

  1. ValidatorRegistry.validate(message) — applies validators only.
  2. message.validate() — applies generated checks (from .proto options) and also applies all validators registered for that message type.

ValidatorRegistry.validate(message) is useful when you need to run custom logic independently from generated checks, or when you validate external message types that do not have a generated validate() method.

Kotlin
Java
import com.google.protobuf.Timestamp
import io.spine.validation.ValidatorRegistry

val timestamp: Timestamp = // ...

// Applies validators only.
val violations = ValidatorRegistry.validate(timestamp)

// Applies generated checks (if any) and validators registered for the type.
val error = myMessage.validate()
import com.google.protobuf.Timestamp;
import io.spine.validation.ValidatorRegistry;

Timestamp timestamp = /* ... */;

// Applies validators only.
var violations = ValidatorRegistry.validate(timestamp);

// Applies generated checks (if any) and validators registered for the type.
var error = myMessage.validate();

Add a validator 

To register a validator explicitly, call ValidatorRegistry.add() with:

  • the message type the validator applies to, and
  • the validator instance.
Kotlin
Java
import com.google.protobuf.Timestamp
import io.spine.validation.TimestampValidator
import io.spine.validation.ValidatorRegistry

ValidatorRegistry.add(Timestamp::class, TimestampValidator())
import com.google.protobuf.Timestamp;
import io.spine.validation.TimestampValidator;
import io.spine.validation.ValidatorRegistry;

ValidatorRegistry.add(Timestamp.class, new TimestampValidator());

Query registered validators 

To obtain the currently registered validators for a message type, call ValidatorRegistry.get().

Kotlin
Java
import com.google.protobuf.Timestamp
import io.spine.validation.ValidatorRegistry

val validators = ValidatorRegistry.get(Timestamp::class)
import com.google.protobuf.Timestamp;
import io.spine.validation.ValidatorRegistry;

var validators = ValidatorRegistry.get(Timestamp.class);

Remove and replace validators 

To remove all validators registered for a message type, call ValidatorRegistry.remove().

This is also the simplest way to override automatically discovered validators: remove validators for the message type and then add the desired ones.

Kotlin
Java
import com.google.protobuf.Timestamp
import io.spine.validation.ValidatorRegistry

ValidatorRegistry.remove(Timestamp::class)
ValidatorRegistry.add(Timestamp::class, MyTimestampValidator())
import com.google.protobuf.Timestamp;
import io.spine.validation.ValidatorRegistry;

ValidatorRegistry.remove(Timestamp.class);
ValidatorRegistry.add(Timestamp.class, new MyTimestampValidator());

The ${validator} placeholder 

When ValidatorRegistry converts validator-reported violations into ConstraintViolations, it automatically populates the validator placeholder with the validator’s fully qualified class name.

If your error message template references ${validator}, you can format it after validation:

Kotlin
Java
import com.google.protobuf.Timestamp
import io.spine.validation.DetectedViolation
import io.spine.validation.MessageValidator
import io.spine.validation.MessageViolation
import io.spine.validation.ValidatorRegistry
import io.spine.validation.templateString

class MyTimestampValidator : MessageValidator<Timestamp> {
    override fun validate(message: Timestamp): List<DetectedViolation> =
        listOf(
            MessageViolation(
                templateString {
                    withPlaceholders = "Rejected by `\${validator}`."
                }
            )
        )
}

ValidatorRegistry.add(Timestamp::class, MyTimestampValidator())

val timestamp = Timestamp.newBuilder().setNanos(-1).build()
val violation = ValidatorRegistry.validate(timestamp).single()

val placeholder = ValidatorRegistry.VALIDATOR_PLACEHOLDER
val validatorClass = violation.message.placeholderValueMap[placeholder]

// If the template references `${validator}`, it will be substituted here.
val text = violation.message.format()
import com.google.protobuf.Timestamp;
import io.spine.validation.DetectedViolation;
import io.spine.validation.MessageValidator;
import io.spine.validation.MessageViolation;
import io.spine.validation.TemplateString;
import io.spine.validation.TemplateStrings;
import io.spine.validation.ValidatorRegistry;
import java.util.List;

final class MyTimestampValidator implements MessageValidator<Timestamp> {
    @Override
    public List<DetectedViolation> validate(Timestamp message) {
        return List.of(
                new MessageViolation(
                        TemplateString.newBuilder()
                                .setWithPlaceholders("Rejected by ${validator}.")
                                .build()
                )
        );
    }
}

ValidatorRegistry.add(Timestamp.class, new MyTimestampValidator());

var timestamp = Timestamp.newBuilder().setNanos(-1).build();
var violation = ValidatorRegistry.validate(timestamp).get(0);

var placeholder = ValidatorRegistry.VALIDATOR_PLACEHOLDER;
var validatorClass = violation.getMessage().getPlaceholderValueMap().get(placeholder);

// If the template references `${validator}`, it will be substituted here.
var text = TemplateStrings.format(violation.getMessage());

The validator entry is always added to placeholder_value, even if the template does not reference ${validator}.

Clear the registry 

To remove all validators for all message types, call ValidatorRegistry.clear().

This API is typically useful in tests to ensure isolation between test cases.

Kotlin
Java
import io.spine.validation.ValidatorRegistry

ValidatorRegistry.clear()
import io.spine.validation.ValidatorRegistry;

ValidatorRegistry.clear();