Validation Framework

Finatra provides a simple validation framework inspired by JSR-303 and JSR-380.


Constraints are Java interfaces that can be used to annotate a case class field or case class method with specific validation criteria. Similar to JSR-380 specification, the validation framework supports the following built in constraints (for adding new Constraints, see Defining Additional Constraints):

Using a Validator

The validation framework can be used without any dependencies. You can validate a case class’s fields and methods that are annotated with any built in or additional constraints by simply instantiating a Validator and call validate() with the case class.

The call will return a Unit when all validations pass. Otherwise, it will throw a ValidationException, where the errors field contains a list of invalid ValidationResult.

For instance, assuming we have the following case class defined:

case class Things(@Size(min = 1, max = 2) names: Seq[String])

We can now validate that an instance conforms to the constraints, e.g.,

val myThings = Things(Seq.empty[String])
val validator = injector.instance[Validator]
try {
} catch {
  case e: ValidationException =>
    error(e, e.getMessage)

Here the ValidationException would get thrown since the instance does not conform to the constraints. You can iterate over the contained ValidationException#errors to see all the errors which triggered a failed validation. Whereas the following would pass:

val myThings = Things(Seq(“Bob Vila”))
val validator = injector.instance[Validator]
try {
} catch {
  case e: ValidationException =>
    error(e, e.getMessage)

Instantiate a Validator

There are 2 ways to obtain a Validator instance:

  • Through dependency injection. Finatra HttpServer injects a default Validator, you can override it by providing a customized Validator in a TwitterModule, and add that module to your server definition. Please checkout Injecting a customized Validator for instructions.
  • Create a Validator instance in the air when you need it. You can call Validator() to obtain a validator instance with default MessageResolver and default cache size, or create a validator instance with customized messageResolver by calling Validator(messageResolver). You can also leverage Validator.Builder to customize more attributes of the Validator including cacheSize. Please see the example on how to use Builder.


Instantiating a Validator through dependency injection is the recommended way to use the framework. Please avoid creating a Validator in the air if you have already injected it in the object graph.

Define additional constraints

To define new constraints, you can extend the Constraint interface, and define a matching ConstraintValidator that is referred to in the validatedBy field of the new constraint definition.

Define an additional Constraint:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.twitter.finatra.validation.Constraint;

@Constraint(validatedBy = StateConstraintValidator.class)
public @interface StateConstraint {}

Define an additional ConstraintValidator:

import com.twitter.finatra.validation.{ConstraintValidator, MessageResolver, ValidationResult}

class StateConstraintValidator(messageResolver: MessageResolver)
    extends ConstraintValidator[StateConstraint, String](messageResolver) {

  override def isValid(annotation: StateConstraint, value: String): ValidationResult =
      "Please register with state CA"

The validation framework will locate the new StateConstraint and perform the validation logic defined in its matching StateConstraintValidator automatically at run time.

Injecting a customized Validator

You can switch to use another MessageResolver or change the cacheSize of the default Validator.

Provide a customized Validator in a TwitterModule:

import com.twitter.finatra.validation.{Validator, ValidatorModule}
import com.twitter.inject.Injector

object CustomizedValidatorModule extends ValidatorModule {
  override def configureValidator(injector: Injector, builder: Validator.Builder): Validator.Builder =
      .withMessageResolver(new CustomizedMessageResolver())

Override validatorModule in your server definition:

ValidationServer extends HttpServer {
  override val name: String = "validation-server"
  override def validatorModule: TwitterModule = CustomizedValidatorModule

  override protected def configureHttp(router: HttpRouter): Unit = {


By overriding the default validatorModule, you are also replacing the default Validator in `jacksonModule`__, the new Validator will be used to apply the validation logic in `ScalaObjectMapper`__ and during HTTP request parsing. Checkout Integrate with Finatra Jackson framework For more information about how validations works in Finatra Jackson Framework.

Integrate with Finatra Jackson framework

The validation framework integrates with Finatra’s custom case class deserializer to efficiently apply per field and method validations as request parsing is performed.

Assume you have the following HTTP request case class defined:

case class ValidateUserRequest(
  @NotEmpty @Pattern(regexp = "[a-z]+") userName: String,
  @Max(value = 9999) id: Long,
  title: String

And in your controller, you define a Post endpoint as:

post("/validate_user") { _: ValidateUserRequest =>

When you perform a call to the POST /validate_user/, Finatra will deserialize the JSON you passed to the call to a ValidationUserRequest case class, and perform validations of the annotated fields. If any validation fails, the case class will not be created and a CaseClassMappingException will be thrown.

For more information, please refer to JSON Validation Framework.

Method Validations

A method validation is a case class method annotated with @MethodValidation which is intended to be used for validating fields of the cases class. Reasons to use a method validation include:

  • For non-generic validations. @MethodValidation can be used instead of defining a reusable annotation and validator.
  • Cross-field validations (e.g. startDate before endDate)

For an example see the User test case class.

The @MethodValidation annotation also supports specifying an optional fields parameter to state which fields are being evaluated in the validation. If the evaluation fails the resulting exception will contain details about each of the fields specified in the annotation.