2

I'm using Spring Boot 2.4. I have the following controller with a method that accepts a MultipartFile object.

@RestController
public class MyController extends AbstractController

    ...
  @Override
  public ResponseEntity<ResponseData> add(
    ...
      @Parameter(description = "file detail") @Validated @RequestPart("myFile")
          MultipartFile myFile,
    ...
    ) {

I would like to validate that this MultipartFile contains the data that I want (e.g. is of a particular mime type). So I have written the below validator ...

@Documented
@Constraint(validatedBy = MultipartFileValidator.class)
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MultipartFileConstraint {
  String message() default "Incorrect file type.";

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

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

and its implementation class ...

public class MultipartFileValidator
    implements ConstraintValidator<MultipartFileConstraint, MultipartFile> {

  @Override
  public void initialize(final MultipartFileConstraint constraintAnnotation) {
    log.info("\n\n\n\nconstructor called\n\n\n\n");
  }

  @Override
  public boolean isValid(
      MultipartFile file, ConstraintValidatorContext constraintValidatorContext) {
    log.info("Validating file");
    ...
  }
}

However, when I invoke my endpoint, I don't see that my validator is called (for one, the log statement is never printed nor breakpoints hit). What else do I need to do to register my validator for this MultipartFile param?

2 Answers 2

3
+300

As per the Spring Documentation:

Can also be used with method level validation, indicating that a specific class is supposed to be validated at the method level (acting as a pointcut for the corresponding validation interceptor), but also optionally specifying the validation groups for method-level validation in the annotated class.

Applying this annotation at the method level allows for overriding the validation groups for a specific method but does not serve as a pointcut; a class-level annotation is nevertheless necessary to trigger method validation for a specific bean to begin with. Can also be used as a meta-annotation on a custom stereotype annotation or a custom group-specific validated annotation.

So, here we have to keep in mind what are the placement of @Validated and validator annotation. Code:

Controller class : @Validated added at class level and @ValidFile (Custom validator annotation) in the method

@RestController
@Validated
@Slf4j
public  class MyController {

    @RequestMapping("/add")
    public ResponseEntity<ResponseData> add(@ValidFile @RequestParam("file")  MultipartFile file) {

        log.info("File Validated");
        return  ResponseEntity.status(HttpStatus.OK).body(new ResponseData("Valid file received"));
    }
}

Validator Annotation

@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {FileValidator.class})
public @interface ValidFile {
    Class<? extends Payload> [] payload() default{};
    Class<?>[] groups() default {};
    String message() default "Only pdf,xml,jpeg,jpg files are allowed";
}

Validator class

@Slf4j
public class FileValidator implements ConstraintValidator<ValidFile, MultipartFile> {

    @Override
    public void initialize(ValidFile validFile) {
        log.info("File validator initialized!!");
    }

    @Override
    public boolean isValid(MultipartFile multipartFile,
                           ConstraintValidatorContext   constraintValidatorContext) {
        log.info("Validating file");
        String contentType = multipartFile.getContentType();
        assert contentType != null;
        return isSupportedContentType(contentType);
    }
    private boolean isSupportedContentType(String contentType) {
        return contentType.equals("application/pdf")
                || contentType.equals("text/xml")
                || contentType.equals("image/jpg")
                || contentType.equals("image/jpeg");
    }
}

Output : Success:

{
    "message": "Valid file received"
}

Exception handler

 @ExceptionHandler(ConstraintViolationException.class)
 @ResponseStatus(HttpStatus.BAD_REQUEST)
 ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
        return new ResponseEntity<>("Validation error: " + e.getMessage(), HttpStatus.BAD_REQUEST);
 }

Failure:

Validation error: Only pdf,xml,jpeg,jpg files are allowed
Sign up to request clarification or add additional context in comments.

Comments

0

Below is a small example. I hope it will help.

@Component
public class MultipartFileValidator implements Validator {
  @Override
  public boolean supports(Class < ? > clazz) {
    return MultipartFile.class.isAssignableFrom(clazz);
  }

  @Override
  public void validate(Object target, Errors errors) {
    MultipartFile multipartFile = (MultipartFile) target;
    if (multipartFile.isEmpty()) {
      // Add an error message to the errors list
      errors.rejectValue("file", "required.file");
    }
  }
}

1 Comment

Is this meant to replace my MultipartFileConstraint and MultipartFileValidator classes? Also does anything with my controller's method need to change?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.