Working with error messages

When a message violates validation constraints, Spine Validation reports violations as ConstraintViolation entries inside a ValidationError.

Each violation contains a machine-friendly error message (TemplateString) which you can:

  • format for end users,
  • log for diagnostics, and
  • customize in your .proto model.

Where error messages come from 

Every built-in validation option defines a default error message. These messages come as the value of the default_message option declared for each option type.

For example, the pattern option defines the following default message:

message PatternOption {

    // The default error message.
    option (default_message) = "The `${parent.type}.${field.path}` field"
        " must match the regular expression `${regex.pattern}` (modifiers: `${regex.modifiers}`)."
        " The passed value: `${field.value}`.";
    
    // ...
}

When you apply an option in a .proto file, you can override the default message by setting the option’s error_msg field.

Example: custom message for a regex pattern.

import "spine/options.proto";

message CreateAccount {
    string id = 1 [
        (pattern).regex = "^[A-Za-z0-9+]+$",
        (pattern).error_msg = "ID must be alphanumerical in `${parent.type}`. Provided: `${field.value}`."
    ];
}

The placeholders (like ${field.value}) are substituted at runtime when the violation is created.

Each option documents the placeholders it supports next to its error_msg field in spine/options.proto.

Placeholders and TemplateString 

ConstraintViolation.message is a TemplateString:

  • with_placeholders — a template string that may contain placeholders like ${field.path}.
  • placeholder_value — a map from placeholder keys to their runtime values.

The placeholder keys in the map do not include ${} — for example, field.path.

The map may include extra keys that are not referenced by the template, but every placeholder used in with_placeholders must have a corresponding value. Otherwise, the template is invalid.

Formatting messages in code 

To format a TemplateString, use:

  • Kotlin: TemplateString.format() / TemplateString.formatUnsafe()
  • Java: TemplateStrings.format(TemplateString) / TemplateStrings.formatUnsafe(TemplateString)

format() validates that all placeholders have values and throws IllegalArgumentException otherwise. formatUnsafe() does not validate and leaves missing placeholders unsubstituted.

Kotlin
Java
val error = message.validate().orElse(null) ?: return
val violation = error.constraintViolationList.first()
val text = violation.message.format()
var error = message.validate();
if (error.isEmpty()) {
    return;
}
var violation = error.get().getConstraintViolation(0);
var text = TemplateStrings.format(violation.getMessage());

Choosing what to show vs what to log 

For end-user output:

  • format and display ConstraintViolation.message;
  • avoid leaking internal type names unless your product requires them.

For diagnostics and support logs:

  • include type_name and field_path to pinpoint the location of the violation;
  • include the raw template (message.with_placeholders) and the placeholder map for debugging.

Troubleshooting formatting issues 

IllegalArgumentException from format() 

This means with_placeholders references a placeholder that has no entry in placeholder_value.

Recommended actions:

  • treat this as a bug in the option/message definition, and fix the custom error_msg in your .proto;
  • if you can tolerate partial substitution (for example, in logs), use formatUnsafe().

What’s next