Validation in Java Applications
Want to learn more about implementing data validations in Java applications? Check out this post to learn more about data validation using the CUBA platform!
Join the DZone community and get the full member experience.Join For Free
Is there a path for data validation in an elegant, standard, and concise way? Is there a way that doesn't fall into unreadability, helps us to keep most of the data validation logic together, and has most of the code already done for us by developers of popular Java frameworks?
Yes, there is.
For us, developers of the CUBA Platform, it is very important to let our users follow the best practices. We believe that the validation code should be:
- Reusable and following the DRY principle;
- Expressed in a clear and natural way;
- Placed in the place where developers expect it to see;
- Able to check data from different data sources: user input, SOAP or REST calls, etc.
- Aware of concurrency;
- Called implicitly by the application, without the need to call the checks manually;
- Showing clear, localized messages to a user using concise designed dialogs;
- And, following standards.
In this article, I'll be using an application based on the CUBA Platform for all examples. However, since CUBA is based on Spring and EclipseLink, most examples will work for any other Java framework that supports JPA and the bean validation standard.
DB Constraints Validations
Perhaps, the most common and straightforward way of data validation uses DB-level constraints, such as a required flag ('not null' fields), string length, unique indexes, and so on. This way is very natural for enterprise applications, as this class of software is usually heavily data-centric. However, even here, developers do mistakes, defining constraints separately for each tier of an application. This problem is often caused by splitting responsibilities between developers.
Let's take an example that most of you have probably faced. If a spec says that the passport field should have 10 digits in its number, most likely, it will be checked everywhere — by the DB architect in DDL, by the backend developer in the corresponding Entity and REST services, and finally, by the UI developer in client source code. Later on, this requirement changes and the size of the field grows up to 15 digits. Tech Support changes the DB constraint, but for a user, it means nothing since the client side check will not be passed anyway.
Everybody knows the way to avoid this problem — validations must be centralized! In CUBA, this central point of such kind of validation is JPA annotations over entities. Based on this meta information, CUBA Studio generates the correct DDL scripts and applies corresponding validators on the client side.
If JPA annotations get changed, CUBA updates DDL scripts and generates migration scripts, so next time you deploy your project, new JPA-based limitations will be applied to your application's UI and DB.
Despite simplicity and implementation that spans up to DB level and is completely bullet-proof, JPA annotations are limited by the simplest cases that can be expressed in DDL standard without involving DB-specific triggers or stored procedures. So, JPA-based constraints can ensure that the entity field is unique or mandatory or can define a maximum length for a varchar column. Also, you can define a unique constraint to the combination of columns with the
@UniqueConstraint annotation. But, this is pretty much it.
However, in the cases that require more complex validation logic, like checking for maximum and minimum values of a field, validating with an expression, or performing a custom check that is specific to your application, we need to utilize the well-known approach called Bean validation.
All we know, Bean validation is a good practice to follow standards, which normally have a long lifecycle and are battle-proven on thousands of projects. Java Bean validation is an approach that is set in stone in JSR 380, 349 and 303, and their implementations: Hibernate Validator and Apache BVal.
Although this approach is familiar to many developers, it's benefits are often underestimated. This is an easy way to add data validations even for legacy projects, which allows you to express your validations in a clear, straightforward, and reliable way that is as close to your business logic as possible.
Using the Bean validation approach brings a lot of benefits to your project:
- Validation logic is concentrated near your domain model, defining the value and method, and the bean constraint is done in a natural way that allows bringing an OOP approach to the next level.
- The Bean validation standard provides many validation annotations out of the box, like:
@Past, and a less standard like,
@ScriptAssert,and many others.
- You are not limited by predefined constraints and can define your own constraint annotations. You can make a new annotation by combining others or making a brand new one and defining a Java class that will be served as a validator.
- For example, looking at our previous example, we can define a class-level annotation
@ValidPassportNumberto check that the passport number follows the right format, which depends on the country field value.
- You can put constraints not just on fields and classes but also on methods and method parameters. This is called "validation by contract" and is the topic of a later section.
The CUBA Platform, as well as some other frameworks, calls these Bean validations automatically when a user submits the data, so the user would get the error message instantly if validation fails, and you don't need to worry about running these Bean validators manually.
Let's take a look at the passport number example once again, but this time, we'd like to add a couple additional constraints on the entity:
- Person name should have a length of two or more and be a well-formed name. Regexp is quite complex, but Charles Ogier de Batz de Castelmore Comte d'Artagnan passes the check and R2D2 does not
- Person height should be in interval: 0 < height <= 300 centimeters
- Email string should be a properly formatted email address.
So, with all these checks, the Person class looks like this:
I think that the usage of standard annotations, like
@Pattern, and others is quite clear and doesn't need a lot of comments. Let's see how custom
@ValidPassportNumber annotation is implemented.
Our brand new
@ValidPassportNumber checks that the
Person#passportNumber match the regexp pattern specific to each country defined by
First, following the documentation (CUBA or Hibernate docs are good references), we need to mark our entity class with this new annotation and pass groups parameter to it, where
UiCrossFieldChecks.class says that the check should be called after checking all individual fields on the cross-field check stage, and the
Default.class keeps the constraint in the default validation group.
The annotation definition looks like this:
@Target(ElementType.TYPE) defines that the target of this runtime annotation is a class, and
@Constraint(validatedBy = ... ) states that the annotation implementation is in the
ValidPassportNumberValidator class that implements the
ConstraintValidator<...> interface and has the validation code in the
isValid(...) method, in which the code does the actual check in a straightforward way:
That's it. With the CUBA platform, we don't need to write a line of code more than that to get our custom validation working and giving messages to a user if they made a mistake. Nothing too complex, right?
Now, let's check how all this stuff works. CUBA has a few extra goodies — it not only shows error messages to a user but also highlights form fields that haven't passed single-field Bean validations with nice red lines:
Isn't this a neat thing? You have nice error UI feedback in the user's browser just after adding a couple Java annotations to your domain model entities.
Concluding this section, let's briefly list once again what pluses Bean validation for entities has:
- It is clear and readable;
- It allows us to define value constraints right in the domain classes;
- It is extendable and customizable;
- It is integrated with many popular ORMs and the checks are called automatically before changes are saved to a database;
- Some frameworks also run Bean validation automatically when the user submits data in the UI (but if not, it's not hard to call the
- Bean validation is a well-known standard, so there is a lot of documentation on the Internet about it.
But, what shall we do if we need to set a constraint onto a method, a constructor, or some REST endpoint to validate data coming from an external system? Or, if we want to check the method parameters values in a declarative way without writing boring code full of if-else statements in each method, do we need to have such a check?
The answer is simple: Bean validation can be applied to methods as well!
Validation by Contract
Sometimes, we need to make another step and go beyond the application data model state validation. Many methods might benefit from automatic parameters and return values validation. This might be required not just when we need to check data coming to a REST or SOAP endpoint but also when we want to express preconditions and postconditions for method calls. This is so that we can be sure that the input data has been checked before the method body is executed, that the return values are in the expected range, or we want to declaratively express parameters boundaries for better readability.
With Bean validation, constraints can be applied to the parameters and return values of a method or constructors of any Java type to check for their calls' preconditions and postconditions. This approach has several advantages over traditional ways of checking the correctness of parameters and return values:
- The checks don't need to be performed manually in an imperative way (e.g. by throwing
IllegalArgumentExceptionor similar methods). We rather specify constraints declaratively, so we have more readable and expressive code;
- Constraints are reusable, configurable, and customizable — we don't need to write validation code every time we need to do the checks. Less code equals fewer bugs.
- If a class or method returns a value or method parameter that is marked with the
@Validatedannotation, the constraints check would be done automatically by the framework on every method call.
- If an executable is marked with the
@Documentedannotation, then it's pre- and postconditions would be included in the generated JavaDoc.
As the result with the 'validation by contract' approach, we have a clearer, more concise code, which makes it easier to support and understand.
Let's look at what it looks like for a REST controller interface in the CUBA app. The
PersonApiService interface allows us to get a list of persons from the DB with the
getPersons() method and to add a new person to the DB using the
addNewPerson(...) call. And remember: Bean validation is inheritable! In other words, if you annotate some class or field or method with a constraint, all descendants that extend or implement this class or interface would be affected by the same constraint check.
Does this code snippet look pretty clear and readable to you? (With the exception of the
@RequiredView("_local") annotation, which is specific for the CUBA platform and checks that returned Person object has all fields loaded from the
PASSPORTNUMBER_PERSON table). The
@Valid annotation specifies that every object in the collection is returned by the
getPersons() method and needs to be validated against the Person class constraints, as well.
CUBA makes these methods available at the next endpoints:
Let's open the Postman app and ensure that validation is working as expected:
You might have noticed that the example above doesn't validate the passport number. This is because it requires a cross-parameter validation of the
addNewPerson method since the
passportNumber validation regexp pattern depends from the country value. Such cross-parameter checks are a direct equivalent to the class-level constraints for entities!
Cross-parameter validation is supported by JSR 349 and 380; you can consult Hibernate documentation on how to implement custom cross-parameter validators for the class/interface methods.
Beyond Bean Validation
Nothing is perfect, and Bean validation has some limitations, as well:
- Sometimes, you just want to validate a complex object graph state before saving changes to the database. For example, you might need to ensure that all items from an order made by a customer of your e-commerce system could be fit into the shipping boxes. This is quite a heavy operation and doing such checks every time users add new items to their orders isn't the best idea. Hence, such a check might need to be called just once before the order object and its
OrderItemobjects are saved to the database.
- Some checks have to be made inside the transaction. For example, the e-commerce system should check if there are enough items in stock to fulfill the order before committing it to the database. Such a check could be done only from inside the transaction because the system is concurrent and quantities in stock could be changed at any time.
Entity listeners in CUBA are quite similar to PreInsertEvent, PreUpdateEvent, and PredDeleteEvent listeners that JPA offers to a developer. Both mechanisms allow us to check entity objects before or after they get persisted to a database.
It's not hard to define and wire up an entity listener in CUBA; we need to do two things:
- Create a managed bean that implements one of the entity listener interfaces. For validation purposes, three of these interfaces are important:
- Annotate the entity object that plans to track with the
In comparison with the JPA standard (JSR 338, chapter 3.5), CUBA platform's listener interfaces are typed, so you don't need to cast an object argument to start working with the entity. The CUBA platform adds the possibility of entities associated with the current one or calls the
EntityManager to load and change any other entities. All such changes would invoke appropriate entity listener calls, as well.
Also, the CUBA platform supports soft deletion, a feature when entities in the DB are marked as deleted without deleting their records from the DB. So, for soft deletion, the CUBA platform would call the
AfterDeleteEntityListener listeners while standard implementations would call the
Let's look at the example. An event listener Bean connects to an entity class with just one line of code: annotation
@Listeners that accepts a name of the entity listener class:
And, the entity listener implementation may look like this:
Entity listeners are a great choice when you:
- Need to make a data check inside a transaction before the entity object gets persisted to a DB
- Need to check data in the DB during the validation process, for example, to check that we have enough goods in stock to accept the order
- Need to traverse not just a given entity object, like
Order, but visit the object that is in the association or composition with the entity, like
OrderItemsobjects for the
- Want to track insert/update/delete operations for just some of your entity classes. For example, you want to track such events only for the
OrderItementities and don't need to validate changes in other entity classes during the transaction.
CUBA transaction listeners work in a transactional context, but in comparison with entity listeners, they get called for every database transaction.
This gives them the ultimate power — nothing can pass their attention, but the same power also gives them their biggest weaknesses:
- They are harder to write
- They can downgrade performance significantly if performing too many unneeded checks
- They need to be written much more carefully — a bug in a transaction listener might even prevent an application from bootstrapping;
So, transaction listeners are a good solution when you need to inspect many different types of entities with the same algorithm, like feeding data to a custom fraud detector that serves all your business objects.
Let's look at the example that checks if an entity is annotated with the
@FraudDetectionFlag annotation.If yes, it runs the fraud detector to validate it. Once again, please note that this method is called before every DB transaction gets committed in the system, so the code has to try to check as little objects as fast as it can.
To become a transaction listener, a managed Bean should just implement the
BeforeCommitTransactionListener interface and implement the
beforeCommit method. Transaction listeners are wired up automatically when the application starts. CUBA registers all classes that implement the
AfterCompleteTransactionListener as the transaction listeners.
Bean validation (JPA 303, 349 and 980) is an approach that could serve as a concrete foundation for 95% of the data validation cases that happen in an enterprise project. The big advantage of such an approach is that most of your validation logic is concentrated right in your domain model classes. So, it is easy to be found, easy to be read, and easy to be supported. Spring, CUBA, and many other libraries are aware of these standards and call the validation checks automatically during UI input, validated method calls, or ORM persistence process, so validation works like a charm from a developer's perspective.
Some software engineers see validation that impacts an applications domain models as being somewhat invasive and complex – they say that making data checks at the UI level is a good enough strategy. However, I believe that having multiple validation points in UI controls and controllers can be a problematic approach. In addition, validation methods that we discussed here are not perceived as invasive when they are integrated with a framework that is aware of the Bean validators and listeners, and they integrate them to the client level automatically.
In the end, let's formulate a rule of thumb to choose the best validation method:
- JPA validation has limited functionality, but it is a great choice for the simplest constraints on entity classes if such constraints can be mapped to DDL.
- Bean Validation is a flexible, concise, declarative, reusable, and readable way to cover most of the checks that you could have in your domain model classes. This is the best choice, in most cases, once you don't need to run validations inside a transaction.
- Validation by Contract is a Bean validation for method calls. You can use it when you need to check input and output parameters of a method, for example, in a REST call handler.
- Entity listeners: although they are not as declarative as the Bean validation annotations, they are a great place to check big object's graphs or make a check that needs to be done inside a database transaction. For example, when you need to read some data from the DB to make a decision, Hibernate has analogs of such listeners.
- Transaction listeners are a dangerous yet ultimate weapon that works inside the transactional context. Use it when you need to decide at runtime what objects have to be validated or when you need to check different types of your entities against the same validation algorithm.
I hope that this article taught you more about different the validation methods available in Java enterprise applications and gave you a couple ideas on how to improve the architecture of projects that you are working on.
Published at DZone with permission of Mikhail Dyakonov. See the original article here.
Opinions expressed by DZone contributors are their own.