Home > Software engineering >  Java generic type validation
Java generic type validation

Time:09-03

I have a generic super class which I'd like to validate:

abstract class Generic<T> {
    // ... other validated fields

    private T value;
}

Then I have several concrete classes and for each of it I'd like to appy specific validations on a value field.

E.g. for the below class I'd like to ensure that the value field is not less than 0.

class ConcreteInt<Integer> {
//    @Min(0)
}

Another example, for the below class I'd like to ensure that the value is not blank.

class ConcreteString<String> {
//    @NotBlank
}

Where should I put my validation annotations on a concrete classes to make them work?

CodePudding user response:

I would suggest to use the Template design pattern and apply the validations accordingly.

First we have the generic class that accepts any type as T with an abstract method that the children must implement.

public abstract class Generic<T> {
    protected T value;
    abstract T getValue();
}

then we have a child for integer implementation with annotations @NotNull and @PositiveOrZero.

import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;

public class ConcreteInteger extends Generic<Integer> {
    public ConcreteInteger(Integer v) {
        value = v;
    }
    @Override
    @NotNull
    @PositiveOrZero
    public Integer getValue() {
        return value;
    }
}

and a child for string implementation with annotations @NotNull, and @NotBlank. Here we already see that we can have different validations.

package com.yieldlab.reporting.dto.analytics;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

public class ConcreteString extends Generic<String> {
    public ConcreteString(String v) {
        value = v;
    }
    @Override
    @NotNull
    @NotBlank
    public String getValue() {
        return value;
    }
}

But it won't validate if we just use the annotations. We have to invoke the javax.validation.Validator API. Here is one example:

import javax.validation.Configuration;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

public class BeanFieldValidationExample {
    private static final Validator validator;
    static {
        Configuration<?> config = Validation.byDefaultProvider().configure();
        ValidatorFactory factory = config.buildValidatorFactory();
        validator = factory.getValidator();
        factory.close();
    }
    public static void main(String[] args) {
        // it won't validate by just call the constructor
        ConcreteInteger concreteInteger0 = new ConcreteInteger(0);
        System.out.println(concreteInteger0.value);
        ConcreteInteger concreteInteger1 = new ConcreteInteger(1);
        System.out.println(concreteInteger1.value);
        ConcreteInteger concreteIntegerMinus1 = new ConcreteInteger(-1);
        System.out.println(concreteIntegerMinus1.value);
        ConcreteInteger concreteIntegerNull = new ConcreteInteger(null);
        System.out.println(concreteIntegerNull.value);

        ConcreteString concreteString0 = new ConcreteString("some string");
        System.out.println(concreteString0.value);
        ConcreteString concreteString1 = new ConcreteString("");
        System.out.println(concreteString1.value);
        ConcreteString concreteStringNull = new ConcreteString(null);
        System.out.println(concreteStringNull.value);

        // invoking the validator API will in validate our beans validator.validate(concreteInteger0).stream().forEach(BeanFieldValidationExample::printErrorConcreteInteger);
        validator.validate(concreteInteger1).stream().forEach(BeanFieldValidationExample::printErrorConcreteInteger);
        validator.validate(concreteIntegerMinus1).stream().forEach(BeanFieldValidationExample::printErrorConcreteInteger);
        validator.validate(concreteIntegerNull).stream().forEach(BeanFieldValidationExample::printErrorConcreteInteger);

        validator.validate(concreteString0).stream().forEach(BeanFieldValidationExample::printErrorConcreteString);
        validator.validate(concreteString1).stream().forEach(BeanFieldValidationExample::printErrorConcreteString);
        validator.validate(concreteStringNull).stream().forEach(BeanFieldValidationExample::printErrorConcreteString);
    }

    private static void printErrorConcreteInteger(ConstraintViolation<ConcreteInteger> concreteIntegerConstraintViolation) {
        System.out.println(concreteIntegerConstraintViolation.getPropertyPath()   " "   concreteIntegerConstraintViolation.getMessage());
    }

    private static void printErrorConcreteString(ConstraintViolation<ConcreteString> concreteStringConstraintViolation) {
        System.out.println(concreteStringConstraintViolation.getPropertyPath()   " "   concreteStringConstraintViolation.getMessage());
    }
}

then we can see the output:

0
1
-1
null
some string

null
value must be greater than or equal to 0
value must not be null
value must not be blank
value must not be null
value must not be blank

CodePudding user response:

I have found a solution. It's enough to override the getter of the class and place the annotations there:

class ConcreteString<String> {
    @Override
    @NotBlank
    public String getValue(){
        return super.getValue();
    }
}
  • Related