6

I'm using org.springframework.data.domain.Pageable with my @RestController.

How can I validate or limit the page size?

Without any validation, when clients call with size of 10000. The actual pageSize is 2000.

This could lead wrong signal for last page, I think.

How can I validate it and notify clients about it? Say with 400?

Jin Kwon
  • 20,295
  • 14
  • 115
  • 184
  • 1
    Are you sure that you need validation and send feedback to user? you can just cut it to some max value like https://stackoverflow.com/a/44705987/1032167 or https://stackoverflow.com/a/46836539/1032167 and send 400 records instead of 10000. And what kind of client notification do you have in mind? some error response instead of data? – varren Nov 24 '17 at 07:34
  • @varren I'm intending to send 400 for invalid size, because, as I mentioned, sending less number of items could lead clients for last page. – Jin Kwon Dec 19 '17 at 03:00
  • 1
    @varren 400 here referred to the bad request. Not 400 records. – Deepak Apr 17 '19 at 17:26
  • 1
    @JinKwon, I am currently facing that issue. I wanted to throw bad request to the user. Have you achieved it through validation on Pageable? Or you stopped using Pageable and used page and size instead? – Deepak Apr 17 '19 at 17:36

3 Answers3

5

You can write a custom annotation to validate Pageable object

@Constraint(validatedBy = PageableValidator.class)
@Target( { ElementType.METHOD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface PageableConstraint {
    String message() default "Invalid pagination";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    int maxPerPage() default 100;;
}

and Its implementation

public class PageableValidator implements
        ConstraintValidator<PageableConstraint, Pageable> {

    private int maxPerPage;

    @Override
    public void initialize(PageableConstraint constraintAnnotation) {
        maxPerPage=constraintAnnotation.maxPerPage();
    }

    @Override
    public boolean isValid(Pageable value, ConstraintValidatorContext context) {
        return value.getPageSize()<=maxPerPage;
    }
}

and you can use it over your controller like any other javax validation annotations.

@RestController
@RequestMapping("/api")
@Validated
public class EmployeeRestController {

    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/employees")
    public List<Employee> getAllEmployees(@PageableConstraint(message = "Invalid page size",maxPerPage = 400) Pageable pageable) {
        return employeeService.getAllEmployees();
    }
}
Manoj
  • 111
  • 2
  • 8
1

Try using Custom Annotations to validate the Pageable object. If it is not working, try the argument resolver as shown below.

You can create Argument Resolver in your Spring Configuration that is extending WebMvcConfigurerAdapter

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver() {

        @Override
        public Pageable resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
            Pageable p = super.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory);
            if (webRequest.getParameter("per_page") != null) {
                int pageSize = Integer.parseInt(webRequest.getParameter("per_page"));
                if (pageSize < 1 || pageSize > 2000) {
                    message = "Invalid page size";
                }
            }
            if (message != null) {
                Set<CustomConstraintViolation> constraintViolations = new HashSet<>();
                constraintViolations.add(new CustomConstraintViolationImpl(message));
                throw new ConstraintViolationException(constraintViolations);
            }
            return new PageRequest(p.getPageNumber(), p.getPageSize());
        }
    };
    resolver.setMaxPageSize(2000);
    argumentResolvers.add(resolver);
    super.addArgumentResolvers(argumentResolvers);
}

This resolver will make sure the max page size is 2000 for every request to your controller.

You need to throw a ConstraintViolationException when the size is more than 2000. For that you need to create a custom ConstraintViolation interface and implement it

public CustomConstraintViolation implements ConstraintViolation<CustomConstraintViolation> {}

public CustomConstraintViolationImpl implements CustomConstraintViolation {

...

}
Sunil Dabburi
  • 1,442
  • 12
  • 18
  • 2
    The question is how to validate the Pageable object and send bad request (400 http status code) if the user requests beyond the max page size: 2000. – Deepak Apr 17 '19 at 17:30
  • It would be helpful if you could provide the implementation on how you would validate the pageable object. We are looking on how to add that constraint violation on pageable object. – Deepak Apr 22 '19 at 04:32
  • As you can see in my answer, in the argument resolver we are adding constraints violation exception based on page input size. – Sunil Dabburi Apr 23 '19 at 11:30
  • Thank you, I will let you know once I replace my existing logic with this validation. – Deepak Apr 24 '19 at 22:24
  • Did you try @Deepak? – Sunil Dabburi May 28 '19 at 17:37
  • 1
    @SunilDabburi - The problem with `ageable p = super.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory);` doesn't take what consumer has sent in request, its taking `Page request [number: 0, size 2000]`, is there any way if we can take what consumer is sending ? – PAA Feb 11 '20 at 15:02
  • @PAA not sure what you meant. Isn’t it what I’m doing in the next line? We get the passed request parameters to find page and page size the user is looking for. If not provided, the default setting is used. – Sunil Dabburi Feb 12 '20 at 01:10
1

You can simply use configuration

spring.data.web.pageable.max-page-size=400

This will not lead to any error if one tries to get 401 records. Only 400 will be returned. More on stuff that can be configured via spring properties could be found here:

https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html

Ostap
  • 69
  • 6