Since about a month I’m developing some microservices using Javalin, a small, simple and lightweight web-framework that started as a fork from SparkJava. By now it’s a ground-up rewrite, influenced by the Javascript framework koa.js. Don’t let the stuff like Either scare you, we use the Arrow library to make our life a bit more easy :)

When requesting user input from a form, it’s evident that we need to validate our input. Javalin has its own way of doing so (from the docs):

// validate a json body:
val myObject = ctx.bodyValidator<MyObject>()
  .check({ it.myObjectProperty == someValue, "Oops, an error!" })
  .get()

Resulting in the output:

"Request body as MyObject invalid - Oops, an error!"

As you see, not a lot of customisation to alter your error. So when I got the request to return an error in another format to satisfy our front-end application:

{ "fieldname": "invalid",
  "fieldname": "required",
  ...
}

I had to find another solution. This is where Valiktor came to the rescue, using it is easy.

Add the dependency to your application

<!-- https://mvnrepository.com/artifact/org.valiktor/valiktor-core -->
<dependency>
  <groupId>org.valiktor</groupId>
  <artifactId>valiktor-core</artifactId>
  <version>0.8.0</version>
</dependency>

Alter the (translation) messages to your needs

For example create the file: ./src/main/resources/messages_en.properties

org.valiktor.constraints.NotEmpty.message=required
org.valiktor.constraints.NotBlank.message=required
org.valiktor.constraints.Email.message=invalid

Create your custom validator class

class FormValidator {
  companion object Factory {

    fun validateInput(input: UserData) : Either<BadState, UserData> =
      try {
        Either.Right(validate(input) {
          validate(UserData::name).isNotBlank()
          validate(UserData::surname).isNotBlank()
          validate(UserData::email).isNotBlank().isEmail()
        })
      } catch (ex: ConstraintViolationException) {
         Either.Left(ValidationError( ex.constraintViolations
         .mapToMessage(baseName = "messages", locale = Locale.ENGLISH)
         .map { "${it.property}: ${it.message}" } ))
      }
  }
}

The BadState class

sealed class BadState
data class NotAuthorised(val reason : String) : BadState()
data class OtherError(val reason : String) : BadState()
// This is the custom bad state we need :)
data class ValidationError (val errors : List<String>) : BadState()

Then in your controller:

  fun handleYourPost(ctx: Context) {
    when (val result =  createUser(ctx.body<UserData>())) {
      is Either.Right -> {
        ctx.status(201)
        ctx.json(result.b)
      }
      is Either.Left -> {
        when (val error = result.a) {
          is ValidationError -> {
            ctx.status(400)
            ctx.json(error.errors)
          }
          is OtherError -> {
            ctx.status(500)
             ctx.json("something went wrong...")
          }
        }
      }
    }
  }

  fun createUser(userInput: UserData): Either<Any, UserData> {
    return Either.fx<BadState, UserData>{
      val u = FormValidator.validateInput(userInput).bind()  //Do field validation
      ...
    }
  }

So now we’re happy to see the 400 error showing all fields and their errors in a json array.

{ "surname": "required",
  "email": "invalid"
}
shadow-left