Our team upgraded from Hoxton.SR2 to Hoxton.SR7 and we started to have an issue with sorting.

When I look at the request coming from a feing call I see something like that

Hoxton.SR2 = /api/v1/driver/?statuses=Active&page=0&size=10&sort=driver_filename,ASC] Hoxton.SR7 = /api/v1/driver/?statuses=Active&page=0&size=10&sort=driver_filename&sort=ASC]

if you look you have 2 times the same request parameter. sort=driver_filename&sort=ASC.

Is something change in the config that I need to fix or its a bug on your side?

Thank you!

Comment From: jar3czek

It looks like the changes in OpenFeign core library is the cause: https://github.com/OpenFeign/feign/commit/a7b7c01806324126dd844a9a912e309754bc4dc9#diff-52499e1757ad7b3aeb1163f476846de3R190

Every non encoded query param value is splitted by comma to multiple values after this commit.

Comment From: OlgaMaciaszek

Yes, the issue is in Feign Core, however, please see this workaround. Let me know if it solves your issue.

Comment From: royremi

Ill try the work around and let you know,

Thank you All!

Comment From: spring-projects-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: spring-projects-issues

Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.

Comment From: TokenJan

Hi all, I have also met this issue, the suggested workaround by @OlgaMaciaszek is useful in the single sort property situation, but not gonna work in multiple sort properties with directions.

For example: we have two sort properties createdAt and startDate with sort direction DESC. The query string by default looks like /api/v1/xxx/?sort=createdAt&sort=DESC&sort=startDate&sort=DESC, while it may look like /api/v1/xxx/?sort=createdAt,DESC,startDate,DESC when @CollectionFormat(feign.CollectionFormat.CSV) annotation is used. Apparently, neither of the query string is correct.

We found a workaround for this case. We reimplement the PageableSpringEncoder like this:

    public class CustomizedPageableSpringEncoder implements Encoder {

        private final Encoder delegate;

        /**
         * Page index parameter name.
         */
        private static final String PAGE_PARAMETER = "page";

        /**
         * Page size parameter name.
         */
        private static final String SIZE_PARAMETER = "size";

        /**
         * Sort parameter name.
         */
        private static final String SORT_PARAMETER = "sort";

        /**
         * Creates a new PageableSpringEncoder with the given delegate for fallback. If no
         * delegate is provided and this encoder cant handle the request, an EncodeException
         * is thrown.
         *
         * @param delegate The optional delegate.
         */
        public CustomizedPageableSpringEncoder(Encoder delegate) {
            this.delegate = delegate;
        }

        @Override
        public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
            if (supports(object)) {
                if (object instanceof Pageable) {
                    Pageable pageable = (Pageable) object;

                    if (pageable.isPaged()) {
                        template.query(PAGE_PARAMETER, pageable.getPageNumber() + "");
                        template.query(SIZE_PARAMETER, pageable.getPageSize() + "");
                    }

                    applySort(template, pageable.getSort());
                } else if (object instanceof Sort) {
                    Sort sort = (Sort) object;
                    applySort(template, sort);
                }
            } else {
                if (delegate != null) {
                    delegate.encode(object, bodyType, template);
                } else {
                    throw new EncodeException(
                            "PageableSpringEncoder does not support the given object "
                                    + object.getClass()
                                    + " and no delegate was provided for fallback!");
                }
            }
        }

        private void applySort(RequestTemplate template, Sort sort) {
            Collection<String> existingSorts = template.queries().get(SORT_PARAMETER);
            List<String> sortQueries = existingSorts != null ? new ArrayList<>(existingSorts)
                    : new ArrayList<>();
            for (Sort.Order order : sort) {
                sortQueries.add(order.getProperty() + "%2C" + order.getDirection());
            }
            if (!sortQueries.isEmpty()) {
                template.query(SORT_PARAMETER, sortQueries);
            }
        }
    }

    protected boolean supports(Object object) {
        return object instanceof Pageable || object instanceof Sort;
    }
}

The only difference is to separate the sort property and sort direction by %2C instead of , so that the sort pair would not be splitted. (see QueryTemplate.java in feign-core)

I'd like to ask if there is any concern for this workaround, or if there is any better solution for this case. Thanks in advance.