Valiktor Documentation
repository·master·Indexed 19 days ago
https://github.com/valiktor/valiktorA type-safe, fluent DSL for object validation in Kotlin. Valiktor provides an extensible way to define constraints on data models, supporting nested objects, collections, and coroutines. It includes a core engine with 40+ constraints, internationalization (i18n) support, a dedicated test module for assertions, and integrations for Spring WebMvc and WebFlux via the valiktor-spring module. Version 0.12.0.
What's inside Valiktor
- Valiktor is a type-safe, powerful, and extensible fluent DSL designed for validating objects in Kotlin.
Overview of Valiktor modules
masterValiktor is composed of several modules catering to different needs, ranging from the core validation engine to specific integrations for frameworks and data types:
Core Engine
valiktor-core: The primary module containing the validation engine, 40+ constraints, 200+ validation functions for standard Kotlin/Java types, internationalization support, and default formatters.valiktor-test: Provides fluent assertions specifically designed for writing validation tests.
Type-Specific Support
valiktor-javamoney: Support for JavaMoney API (MonetaryAmount).valiktor-javatime: Support for JavaTime API (LocalDate,LocalDateTime,OffsetDateTime,ZonedDateTime).valiktor-jodamoney: Support for Joda-Money API (Money,BigMoney).valiktor-jodatime: Support for Joda-Time API (LocalDate,LocalDateTime,DateTime).
Spring Framework Integration
valiktor-spring: Integration for Spring WebMvc and WebFlux. Includes exception handlers forConstraintViolationException(Valiktor), andInvalidFormatException/MissingKotlinParameterException(Jackson).valiktor-spring-boot-autoconfigure: Provides auto-configuration forvaliktor-springusing properties and WebMvc/WebFlux exception handlers.valiktor-spring-boot-starter: A Spring Boot Starter that bundles bothvaliktor-springandvaliktor-spring-boot-autoconfigure.
How Valiktor works
masterValiktor uses a type-safe DSL to validate object properties. The primary entry point is
org.valiktor.validate, which takes an object and a lambda defining the constraints. If any constraint is violated, aConstraintViolationExceptionis thrown. This exception contains a set ofConstraintViolationobjects, each specifying the property name, the invalid value, and the violated constraint.To handle validation errors, catch
ConstraintViolationExceptionand iterate over itsconstraintViolationsproperty.try { validate(employee) { validate(Employee::id).isPositive() validate(Employee::name).isNotEmpty() } } catch (ex: ConstraintViolationException) { ex.constraintViolations .map { "${it.property}: ${it.constraint.name}" } .forEach(::println) }Internationalization (i18n) in Valiktor
masterValiktor decouples validation logic from error messaging. You can convert
ConstraintViolationobjects into human-readableConstraintViolationMessageobjects using theorg.valiktor.i18n.mapToMessageextension function.Parameters:
baseName: The prefix for message properties (default:org/valiktor/messages).locale: Thejava.util.Localeto use (default: system default).
Supported Locales:
ca(Catalan),de(German),en(English),es(Spanish),ja(Japanese),pt_BR(Portuguese/Brazil).Customizing Messages: Overwrite messages by adding keys to your bundle. The key format is the qualified class name of the constraint plus the
.messagesuffix (e.g.,org.valiktor.constraints.NotEmpty.message).try { validate(employee) { validate(Employee::id).isPositive() } } catch (ex: ConstraintViolationException) { ex.constraintViolations .mapToMessage(baseName = "messages", locale = Locale.ENGLISH) .map { "${it.property}: ${it.message}" } .forEach(::println) }Install Valiktor
masterAdd the
valiktor-coredependency to your project using Gradle (Groovy or Kotlin DSL) or Maven. The current version is0.12.0.// Gradle (Groovy) implementation 'org.valiktor:valiktor-core:0.12.0'// Gradle (Kotlin DSL) implementation("org.valiktor:valiktor-core:0.12.0")<!-- Maven --> <dependency> <groupId>org.valiktor</groupId> <artifactId>valiktor-core</artifactId> <version>0.12.0</version> </dependency>Create a custom message formatter
masterIf constraints contain parameters that require specific formatting (like dates or money) instead of the default
toString(), you can implement theorg.valiktor.i18n.Formatterinterface.Option 1: Programmatic Registration Implement
Formatter<T>and add it toorg.valiktor.i18n.Formatters:object CustomFormatter : Formatter<Custom> { override fun format(value: Custom, messageBundle: MessageBundle) = value.toString() } Formatters[Custom::class] = CustomFormatterOption 2: SPI (Service Provider Interface) Implement
org.valiktor.i18n.FormatterSpiand register it inMETA-INF/services/org.valiktor.i18n.FormatterSpi.object CustomFormatter : Formatter<Custom> { override fun format(value: Custom, messageBundle: MessageBundle) = value.toString() } Formatters[Custom::class] = CustomFormatterValidate nested object properties
masterValiktor supports recursive validation of nested objects. You can nest
validatecalls within the main validation block to traverse the object graph. The resulting property paths in aConstraintViolationwill reflect the nesting (e.g.,company.city.name).try { validate(employee) { validate(Employee::company).validate { validate(Company::city).validate { validate(City::name).isNotEmpty() } } } } catch (ex: ConstraintViolationException) { // property will be 'company.city.name' }Validate array and collection properties
masterTo validate elements within a collection or array, use the
validateForEachfunction. This allows you to apply constraints to every element in the collection. When a violation occurs, the property path will include the index of the invalid element (e.g.,dependents[0].name).try { validate(employee) { validate(Employee::dependents).validateForEach { validate(Dependent::name).isNotEmpty() } } } catch (ex: ConstraintViolationException) { // property will be 'dependents[0].name' }Spring integration for RESTful APIs
masterThe
valiktor-springmodule provides automatic exception handling for Spring WebMvc and WebFlux. When aConstraintViolationExceptionis thrown, the handlers return an HTTP422 Unprocessable Entitystatus with a JSON payload describing the errors.Payload Format:
{ "errors": [ { "property": "string", "value": "any", "message": "string", "constraint": { "name": "string", "params": [{ "name": "string", "value": "any" }] } } ] }Spring Boot Configuration: Use the
valiktor-spring-boot-starterfor auto-configuration. You can configure the message bundle name via:- YAML:
valiktor.base-bundle-name: messages - Properties:
valiktor.baseBundleName=messages
- YAML:
Create a custom validation constraint
masterCreating a custom validation involves three steps:
- Define the Constraint: Implement
org.valiktor.Constraint. ThemessageParamsproperty (aMap<String, *>) is used to pass variables into the error message. - Create the Extension Function: Implement an extension function on
org.valiktor.Validator<E>.Property<T>. Use thevalidatehelper to link the constraint to the logic. For coroutines, usecoValidate. - Add i18n Messages: Add the constraint key to your message bundles (e.g.,
org.valiktor.constraints.MyConstraint.message=Value must be {param}).
// 1. Define Constraint data class Between<T>(val start: T, val end: T) : Constraint // 2. Create Extension fun <E> Validator<E>.Property<Int?>.isBetween(start: Int, end: Int) = this.validate(Between(start, end)) { it == null || it in start.rangeTo(end) } // 3. Usage validate(employee) { validate(Employee::age).isBetween(start = 1, end = 99) }- Define the Constraint: Implement
Test assertions with Valiktor
masterThe Valiktor test module provides a fluent DSL for asserting validation failures in unit tests.
shouldFailValidation<T> { ... }: Asserts that the block throws aConstraintViolationException..verify { ... }: Allows inspecting specific violations.expect(property, value, constraint): Checks if a specific property failed with a specific value and constraint.expectAll(collection) { ... }: Validates all elements in a collection.expectElement { ... }: Validates a specific element within a collection loop.
shouldFailValidation<Employee> { // code that triggers validation }.verify { expect(Employee::name, " ", NotBlank) expectAll(Employee::dependents) { expectElement { expect(Dependent::name, " ", NotBlank) } } }Enable Valiktor WebMvc exception handling in Spring Boot
masterWhen using Spring WebMvc,
ValiktorWebMvcAutoConfigurationautomatically configures specialized exception handlers to translate Valiktor validation errors into appropriate HTTP responses.This auto-configuration triggers if:
org.springframework.web.servlet.DispatcherServletis present on the classpath.- A
ValiktorExceptionHandlerbean is already defined.
It provides the following handlers automatically (unless you define your own beans):
ConstraintViolationExceptionHandler: Handles standard Valiktor constraint violations.InvalidFormatExceptionHandler: Handles JacksonInvalidFormatException(requires Jackson on classpath).MissingKotlinParameterExceptionHandler: Handles Kotlin-specific missing parameter exceptions (requiresjackson-module-kotlinon classpath).