Loading...

Java Bean Validation is an Anti-Pattern

Clean Code
March 20, 2022
4 minutes to read
Share this post:

The javax.validation package is widely used in our industry. And I don’t like it. I believe using bean validation is an anti-pattern. It hides business relevant constraints, it leaves the choice when a validation happens to other framework code, and I even saw cases where developers expected that the validation “just had to take place”, but it never happened. Of course, there was also no test for it. And speaking about tests - testing these business relevant constraints is painful as well.

In a Spring/JPA application, I recently saw code like this:

@Entity
class AppUser(
    @Email
    var email: String,
) {
    @Id
    val id: UUID = UUID.randomUUID()
}

The business constraint is that only valid email addresses are allowed for the AppUser. The @Email annotation is part of the javax.validation package, and it instructs the validator to check if the property is a valid email address.

But when we run this code, no exception is thrown:

@Service
class AppUserService(private val userRepository: AppUserRepository) {
    @EventListener(ApplicationStartedEvent::class)
    fun createUser() {
        userRepository.save(AppUser("This is not an email address"))
    }
}

So, we end up with a business requirement - that only valid e-mail addressees are supposed to be stored - and as a reader of the code we also expect that this is the case, but actually nothing happens. And of course there’s no test to check if that requirement is fulfilled.

Testing is hard

Speaking about tests - testing it is hard. Since you move the responsibility to validate your business requirements to " some" part of the framework, you can’t write a unit test. You must write an integration test to check if your validation does what you expect. This is bad for many reasons. Especially since integration tests are heavy. And booting up your application for such a small requirement is an overkill.

Kotlin and Bean Validation

But I didn’t answer why above check is not performed. The issue in combination with Kotlin is that we didn’t annotate the backing property but the constructor parameter. javax.validation does not pick this annotation up, and therefore it is never validated. To fix that we actually need to annotate the property with @field:Email. In my opinion this example went from bad to worse. We did not protect our code from constructing the entity with invalid data, we even built up the expectation of the reader that this can only contain valid email addresses, but that is actually never checked. And of course we never test it.

All of above thought brings me to the conclusion:

Don’t use Java Bean Validation at all!

The JSR is unnecessary. Validation is nothing we should hide. We validate our data because it checks for certain business constraints. This is business code. It belongs into the main flow of our application. We should not move it to some magic library and rely on the framework. We also not even save much by using these annotations. Most of the checks are super easy if statements. And this is exactly what it should be!

Instead, do your validations in the constructor. Your code must not allow to construct an object with invalid data. So above entity I’d remodel like this:

@Entity
class AppUser(
    var email: String,
) {
    @Id
    val id: UUID = UUID.randomUUID()

    init {
        if (!EmailValidator.isValid(email)) {
            throw IllegalStateException("Email address is invalid")
        }
    }
}

Note “Even cleaner: Kotlins check function” In the Kotlin standard library exists a check function. Using it over a simple if has some benefits. E.g. it evaluates the provided message lazily and just uses a single line instead of three. So, above code can be written as:

init {
  	check(!EmailValidator.isValid(email)) { "Email address is invalid" }
}

It is easy to read, simple and - from a runtime view - unambiguous. Writing a unit test for this constraint is a no-brainer as well. Java Bean Validation is an anti-pattern!

Have you heard of Marcus' Backend Newsletter?

New ideas. Twice a week!
Top