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?
CodePudding user response:
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");
}
}
}
CodePudding user response:
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