Server-Side Validators Using Functional Interfaces
Given where Java is used, chances are that you've written a few server-side validators. Here's how some tools from Java 8 can improve functionality and code readability.
Join the DZone community and get the full member experience.
Join For FreeNote: This post is inspired by and is an attempt at creating an extension to this post on Medium. As such, it will use some of the code in that post by Joel Planes.
As a Java developer, the most common task we have to do is to write some server-side validations for our model data so as to validate the incoming objects to our application. Sure there are frameworks like Hibernate Validator that are used to perform these validations, but sometimes, they are just not an option.
For example, I recently had to validate objects coming to me in RabbitMQ. Here, the @Validate annotation would not work how I wanted, so the only option I had was to use a custom validator. We will see, in this blog post, a nice way to validate incoming objects with our own framework created using wonderful features from Java 8. We will be writing a basic null and empty check validator, which can be further extended to other kinds of validations, too.
This post assumes that you have the working knowledge of Java 8 features and functionalities. If you don’t, no need to worry! Here are the some of the posts that you should read before looking at this one.
Okay, now let's learn all about our fancy new Java 8 validator.
Let's say we have an Employee class, shown below, and we get an Employee object from RabbitMQ or Kafka or some other infrastructure component. Here is what the Employee class looks like:
public class Employee {
private String firstName;
private String lastName;
private String email;
private Integer age;
public Employee(String firstName, String lastName, String email, int age) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.age = age;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
We see there are quite a number of fields in the Employee class, and there is the possibility that each one of them can be set to null by the application producing it. So we write a simple validator to check the null and empty constraints against our Employee object. Here is the code for a simple validator.
public class OldValidator implements EmployeeValidator {
public void validate(Employee employee) throws EmployeeException {
if (employee.getFirstName() == null || employee.getFirstName().isEmpty())
throw new EmployeeException("Please specify valid firstname");
if (employee.getFirstName().length() < 2)
throw new EmployeeException("Please specify valid firstname");
if (employee.getFirstName().length() > 100)
throw new EmployeeException("Please specify valid firstname");
if (employee.getLastName() == null || employee.getLastName().isEmpty())
throw new EmployeeException("Please specify valid lastname");
if (employee.getLastName().length() < 2)
throw new EmployeeException("Please specify valid lastname");
if (employee.getLastName().length() > 100)
throw new EmployeeException("Please specify valid lastname");
if (employee.getEmail() == null || employee.getEmail().isEmpty())
throw new EmployeeException("Please specify valid email");
if (employee.getEmail().length() < 3)
throw new EmployeeException("Please specify valid email");
if (employee.getEmail().length() > 100)
throw new EmployeeException("Please specify valid email");
if (employee.getAge() == null || employee.getAge() == 0)
throw new EmployeeException("Please specify valid age");
if (employee.getAge() < 18)
throw new EmployeeException("Please specify valid age");
if (employee.getAge() > 60)
throw new EmployeeException("Please specify valid age");
}
}
Even if the validator above works, it just doesn’t look that great and kind of feels like a novice programmer’s work. How do we avoid this? Enter our Java 8 validator.
First of all, we have our functional interface Validation below:
@FunctionalInterface
public interface Validation <K> {
public GenericValidationResult test(K param);
default Validation <K> and(Validation <K> other) {
return (param) -> {
GenericValidationResult result = this.test(param);
return !result.isValid() ? result : other.test(param);
};
}
default Validation <K> or(Validation <K> other) {
return (param) -> {
GenericValidationResult result = this.test(param);
return result.isValid() ? result : other.test(param);
};
}
}
We have a @FunctionalInterface that has an abstract method test, an ANDing default function, and an ORing default function. GenericValidationResult is the response of the test function.
We then write a concrete implementation for above interface.
public class GenericValidation <K> implements Validation <K> {
private Predicate <K> predicate;
public static <K> GenericValidation <K> from(Predicate <K> predicate) {
return new GenericValidation <K> (predicate);
}
private GenericValidation(Predicate <K> predicate) {
this.predicate = predicate;
}
@Override
public GenericValidationResult test(K param) {
return predicate.test(param) ? GenericValidationResult.ok() : GenericValidationResult.fail();
}
}
We have a static method “from”, which takes in a predicate (or a condition) and returns a GenericValidation. We also have overridden the test method that has the implementation.
Here is the GenericValidationResult class, which has the methods ok, fail, and isValid. It also has the method getFieldNameIfInvalid, which will optionally return the invalid field’s name if the constraint is violated.
public class GenericValidationResult {
private boolean valid;
public boolean isValid() {
return valid;
}
public static GenericValidationResult ok() {
return new GenericValidationResult(true);
}
private GenericValidationResult(boolean valid) {
this.valid = valid;
}
public static GenericValidationResult fail() {
return new GenericValidationResult(false);
}
public Optional < String > getFieldNameIfInvalid(String field) {
return this.valid ? Optional.empty() : Optional.of(field);
}
}
Now let's create a ValidatorUtil that will be used to statically call these validation methods. In the code below, note how the validations are declared using the functional interface we created above.
package com.tuturself.java8validator.validators.genericvalidator;
public class ValidatorUtil {
public static final Validation <String> notNullString = GenericValidation.from(s -> s != null);
public static final Validation <String> notEmptyString = GenericValidation.from(s -> !s.isEmpty());
public static final Validation <Integer> notNullInteger = GenericValidation.from(s -> s != null);
public static final Validation <Integer> greaterThanZero = GenericValidation.from(s -> s > 0);
public static final Validation <String> stringMoreThan(int size) {
return GenericValidation.from(s -> ((String) s).length() > size);
};
public static final Validation <String> stringLessThan(int size) {
return GenericValidation.from(s -> ((String) s).length() < size);
};
public static final Validation <String> stringBetween(int morethan, int lessThan) {
return stringMoreThan(morethan).and(stringLessThan(lessThan));
};
public static final Validation <Integer> integerMoreThan(int limit) {
return GenericValidation.from(s -> s > limit);
};
public static final Validation <Integer> integerLessThan(int size) {
return GenericValidation.from(s -> s < size);
};
public static final Validation <Integer> integerBetween(int morethan, int lessThan) {
return integerMoreThan(morethan).and(integerLessThan(lessThan));
};
}
See how nicely we can define our validations using the functional interface> This kind of code looks very easy to read and a lot easier to test.
Let us now actually write a class to validate our Employee object.
public class Java8Validator implements EmployeeValidator {
@Override
public void validate(Employee employee) throws EmployeeException {
StringBuilder errorFields = new StringBuilder();
errorFields.append(ValidatorUtil.notNullString.and(ValidatorUtil.notEmptyString)
.and(ValidatorUtil.stringBetween(1, 100)).test(employee.getFirstName())
.getFieldNameIfInvalid(" Please specify valid firstname ").orElse(""));
errorFields.append(ValidatorUtil.notNullString.and(ValidatorUtil.notEmptyString)
.and(ValidatorUtil.stringBetween(1, 100)).test(employee.getLastName())
.getFieldNameIfInvalid(" Please specify valid lastname ").orElse(""));
errorFields.append(
ValidatorUtil.notNullString.and(ValidatorUtil.notEmptyString).and(ValidatorUtil.stringBetween(3, 100))
.test(employee.getEmail()).getFieldNameIfInvalid(" Please specify valid email ").orElse(""));
errorFields.append(ValidatorUtil.notNullInteger.and(ValidatorUtil.greaterThanZero)
.and(ValidatorUtil.integerBetween(18, 60)).test(employee.getAge())
.getFieldNameIfInvalid(" Please specify valid age ").orElse(""));
String errors = errorFields.toString();
if (!errors.isEmpty()) {
throw new EmployeeException(errors);
}
}
}
Again, we have reduced our lines of code for validation significantly, and the implementation looks much cleaner and better than it looked in the OldValidator class. Also, if you look closely, this new validator also lets you throw errors for all the invalid fields at once in a single exception, as opposed to the earlier validator, where you had to throw a single exception for a field.
Let us now write some unit tests to test out our old validator and fancy Java 8 validator.
public abstract class EmployeeValidatorTest {
public abstract EmployeeValidator getInstance();
@Test
public void employee_isComplete_validationSucceed() {
try {
getInstance().validate(new Employee("ABCD", "XYZS", "jon@abc.mail", 37));
} catch (EmployeeException e) {
fail("Should have been valid and therefore not thrown an exception");
}
}
@Test
public void employee_withoutFirstName_validationFail() {
try {
getInstance().validate(new Employee(null, "XYZS", "jon@abc.mail", 37));
fail("Should have had EmployeeException containing 'valid firstname'");
} catch (EmployeeException e) {
assertTrue(e.getMessage().contains("valid firstname"));
}
}
@Test
public void employee_shortFirstName_validationFail() {
try {
getInstance().validate(new Employee("A", "XYZS", "jon@abc.mail", 37));
fail("Should have had EmployeeException containing 'valid firstname'");
} catch (EmployeeException e) {
assertTrue(e.getMessage().contains("valid firstname"));
}
}
@Test
public void employee_wrongEmail_validationFail() {
try {
getInstance().validate(new Employee("ABCD", "XYZS", "1", 37));
fail("Should have had EmployeeException containing 'valid email'");
} catch (EmployeeException e) {
assertTrue(e.getMessage().contains("valid email"));
}
}
@Test
public void employee_underAge_validationFail() {
try {
getInstance().validate(new Employee("ABCD", "XYZS", "jon", 16));
fail("Should have had EmployeeException containing 'valid age'");
} catch (EmployeeException e) {
assertTrue(e.getMessage().contains("valid age"));
}
}
@Test
public void employee_overAge_validationFail() {
try {
getInstance().validate(new Employee("ABCD", "XYZS", "jon", 65));
fail("Should have had EmployeeException containing 'valid age'");
} catch (EmployeeException e) {
assertTrue(e.getMessage().contains("valid age"));
}
}
}
Implementation 1:
public class OldValidatorTest extends EmployeeValidatorTest {
@Override
public EmployeeValidator getInstance() {
return new OldValidator();
}
}
Implementation 2:
public class Java8ValidatorTest extends EmployeeValidatorTest {
@Override
public EmployeeValidator getInstance() {
return new Java8Validator();
}
}
Here are the test results:
The code for this example is in this GitHub repository.
If you have any comments or suggestions for this post, please let me know in the comments below.
Happy learning!
Published at DZone with permission of Shreyash Thakare. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments