1

I am trying to validate the postal code in my but the approach i am thinking of is not working out and I can't understand why.

I created a Validator, that hast to throw a ValidationException if it's not valid.

@Service
public class ZipCodeValidator{

    public void validate(String zipCode){
        validateNotEmpty(zipCode);
        validateHasNoSpaces(zipCode);
    }

    private void validateNotEmpty(String zipCode){
       if (zipCode.length() != 0){
           throw new ValidationException("Postal Code can't be empty");
       }
    }

    private void validateHasNoSpaces(String zipCode) {
        if (zipCode.contains(" ")){
            throw new ValidationException("Postal Code can't contain spaces");
        }
    }

}

In my service, where i receive the postal code I want to throw my custom exception (to which i pass the error message) like this:

    try{
        validator.validate(zipCode);
    }catch (ValidationException ex){
        throw new ZipCodeValidationException(ex.getMessage());
    }

However it doesn't seem to work, that exception is not caught so the program runs further.

What am I doing wrong?

Is the whole approach wrong? What would you recommend?

Here's my custom Exception

public class ZipCodeValidationException extends Exception{
    public ZipCodeValidationException(String message) {
        super(message);
    }
}
Diana
  • 935
  • 10
  • 31
  • u should use Exception rather than ValidationException everywhere. – priyranjan Dec 30 '20 at 14:52
  • where are you trying to validate ZipCode inside Controller ? there is specific place if you are doing it inside controller – silentsudo Dec 30 '20 at 15:02
  • @silentsudo it is currently happening in the service that i call from the controller. But i am quite a Junior, so i have no idea if that's the way to do it. – Diana Dec 30 '20 at 15:14
  • can you show the `zipCode` code value ? looks like there is bug in your code @Diana – Ryuzaki L Dec 30 '20 at 15:17
  • @Deadpool the value of zipCode is `""` , after I changed `ValidationException` to `Exception` my code started doing what it was supposed to do. – Diana Dec 30 '20 at 15:21
  • @Diana Just a suggestion on a different point. In case you have the liberty to use third party libraries, you could use the StringUtils.isBlank() function from Apache lib, to perform these simple String validations. http://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html – Sandeep Lakdawala Dec 30 '20 at 15:36
  • why are you suggesting third party library, isn;t this sufficient? https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/ObjectUtils.html#isEmpty-java.lang.Object- and also https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/StringUtils.html#hasLength-java.lang.CharSequence- – silentsudo Dec 31 '20 at 04:47
  • 1
    @silentsudo yes agree, the ones that you have posted would be more apt ,since they belong to Spring. Its just that Apache utility libs are also pretty common. – Sandeep Lakdawala Dec 31 '20 at 09:58

2 Answers2

1

I recommend the following:

  • to process all exceptions in universal place as ExceptionHandler class, for more details see: https://www.baeldung.com/exception-handling-for-rest-with-spring
  • you can extend ValidationException from RuntimeException, that approach will allow unloading the code from try-catch constructions
  • @Service annotation is not quite right for converters or validators, as rule @Service class contains business logic, for helpers classes will be better use @Component, in total no differences between these two annotations only understanding which layer of application that component has

Please share the code for more suggestions and help.

saver
  • 2,541
  • 1
  • 9
  • 14
1

Hi Please find my answer in 2 steps, first the correct and then the second the suggested way to implement.

Correction:

Please use ObjectUtils.isEmpty(arg) for checking if string is 0 length or null. Here is the modified version of your code

public interface ZipcodeService {
        void validate(@Zipcode String zipCode) throws ZipCodeValidationException;
    }

    @Service
    public static class ZipcodeServiceImpl implements ZipcodeService {
        private final ZipCodeRegexMatcher zipCodeRegexMatcher;

        public ZipcodeServiceImpl() {
            zipCodeRegexMatcher = new ZipCodeRegexMatcher();
        }

        @Override
        public void validate(String zipCode) throws ZipCodeValidationException {
            // uncomment for Regex Validation
            // boolean valid = zipCodeRegexMatcher.isValid(zipCode);
            // uncomment for Simple text validation
            final boolean valid = !ObjectUtils.isEmpty(zipCode);

            if (!valid) {
                throw new ZipCodeValidationException("Invalid zipcode");
            }
        }
    }

This is how the caller looks like from Controller

@GetMapping(path = "dummy")
    public String getDummy(@RequestParam("zipcode") String zipCode) {
        try {
            zipcodeService.validate(zipCode);
            return zipCode;
        } catch (ZipCodeValidationException e) {
            return e.getMessage();
        }
    }

Suggested way:

add following entry to pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Create Annotation and Validator as given below

@Constraint(validatedBy = {ZipcodeValidator.class})
    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Zipcode {
        String message() default "Invalid Zipcode value";

        Class<?>[] groups() default {};

        Class<? extends Payload>[] payload() default {};
    }

    public static class ZipcodeValidator implements ConstraintValidator<Zipcode, String> {
        private final ZipCodeRegexMatcher zipCodeRegexMatcher;

        public ZipcodeValidator() {
            zipCodeRegexMatcher = new ZipCodeRegexMatcher();
        }

        @Override
        public boolean isValid(String zipCode, ConstraintValidatorContext constraintValidatorContext) {
            return zipCodeRegexMatcher.isValid(zipCode);
        }
    }

Once this setup is done, head over to Controller class and annotated class with @Validated and field you want to have validation on with the Custom Annotation i.e Zipcode we have just created. We are creating a Custom Validator in this case ZipcodeValidator by extending ConstraintValidator.

This is how the caller looks like:

    @GetMapping
    public String get(@Zipcode @RequestParam("zipcode") String zipCode) {
        return zipCode;
    }

On Failed validation, it throws javax.validation.ConstraintViolationException: get.zipCode: Invalid Zipcode value which you can customize according to your need by using ControllerAdvice.

You can also use @Zipcode annotation at the service level and it works the same way. Regarding ZipCodeRegexMatcher instead of creating it inside the constructor you can create a bean and inject that dependency. It is a simple class that has regex for zipcode and performs validation.

public static class ZipCodeRegexMatcher {
        public static final String ZIP_REGEX = "^[0-9]{5}(?:-[0-9]{4})?$";
        private final Pattern pattern;

        public ZipCodeRegexMatcher() {
            pattern = Pattern.compile(ZIP_REGEX);
        }

        public boolean isValid(String zipCode) {
            return pattern.matcher(zipCode).matches();
        }
    }

The entire code is located here

silentsudo
  • 6,730
  • 6
  • 39
  • 81