Skip to content

Migrate MediaType.sortByQualityValue() to local implementation#3856

Open
gdgenchev wants to merge 2 commits intocloudfoundry:developfrom
gdgenchev:migrate-media-type-sort
Open

Migrate MediaType.sortByQualityValue() to local implementation#3856
gdgenchev wants to merge 2 commits intocloudfoundry:developfrom
gdgenchev:migrate-media-type-sort

Conversation

@gdgenchev
Copy link
Copy Markdown
Contributor

@gdgenchev gdgenchev commented Apr 22, 2026

MediaType.sortByQualityValue() was deprecated in Spring 6 and later removed in Spring 7. Copy the sorting logic into a utility class to preserve content negotiation behavior that respects client quality value preferences from Accept headers.

Exact copy of Spring 6:

https://github.com/spring-projects/spring-framework/blob/9f431e2eac1b6d8d5ca385d0cc367bac94dd37e7/spring-web/src/main/java/org/springframework/http/MediaType.java#L906-L911

https://github.com/spring-projects/spring-framework/blob/9f431e2eac1b6d8d5ca385d0cc367bac94dd37e7/spring-web/src/main/java/org/springframework/http/MediaType.java#L927-L965

https://github.com/spring-projects/spring-framework/blob/9f431e2eac1b6d8d5ca385d0cc367bac94dd37e7/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java#L390-L444 (+ some additional tests for the utility method)

This maintains backward compatibility.


The other approach would be to switch to https://github.com/spring-projects/spring-framework/blob/c6096ff6e5e7de92cf6a3466e211d72b401a0178/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java#L351-L358

but it will not be backward compatible and we will have to change some ITs that cover this.

…ng 7

Spring 7 removed MediaType.sortByQualityValue() and QUALITY_VALUE_COMPARATOR
that were deprecated in Spring 6. Copy the sorting logic into a new
MediaTypeComparators utility class to preserve content negotiation behavior
that respects client quality value preferences from Accept headers.

https://github.com/spring-projects/spring-framework/blob/9f431e2eac1b6d8d5ca385d0cc367bac94dd37e7/spring-web/src/main/java/org/springframework/http/MediaType.java#L927-L965
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR preserves Spring content-negotiation behavior after MediaType.sortByQualityValue() was removed (Spring 7) by introducing a local MediaType quality-value comparator and updating call sites to use it.

Changes:

  • Added MediaTypeComparators.BY_QUALITY_VALUE, mirroring Spring 6’s quality-value comparator logic.
  • Replaced MediaType.sortByQualityValue(...) with acceptedMediaTypes.sort(MediaTypeComparators.BY_QUALITY_VALUE) in two renderers/views.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
server/src/main/java/org/cloudfoundry/identity/uaa/web/ConvertingExceptionView.java Switches Accept media type sorting to the new local comparator.
server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeComparators.java Introduces a Spring-6-compatible MediaType quality-value comparator.
server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/error/DefaultOAuth2ExceptionRenderer.java Switches Accept media type sorting to the new local comparator.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeUtils.java Outdated
- Rename MediaTypeComparators to MediaTypeUtils for better semantics
- Add sortByQualityValue() utility method to handle immutable lists
- Fix usages to create mutable copies before sorting
- Remove unnecessary if-else in ConvertingExceptionView
- Add comprehensive unit tests including parameterized tests
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR preserves UAA’s existing content-negotiation behavior by replacing the removed MediaType.sortByQualityValue(...) API with an equivalent local implementation, ensuring Accept header q (quality) preferences are still respected when selecting response converters.

Changes:

  • Added MediaTypeUtils with a BY_QUALITY_VALUE comparator and sortByQualityValue(...) method matching Spring 6 behavior.
  • Updated server-side rendering paths to use MediaTypeUtils.sortByQualityValue(...) instead of the removed Spring API.
  • Added unit tests covering sorting behavior and comparator edge cases (including stable ordering expectations).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeUtils.java Introduces the local comparator + sort helper to replace removed Spring API.
server/src/main/java/org/cloudfoundry/identity/uaa/web/ConvertingExceptionView.java Switches Accept sorting to the new local utility before choosing a message converter.
server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/error/DefaultOAuth2ExceptionRenderer.java Switches Accept sorting to the new local utility before choosing a message converter.
server/src/test/java/org/cloudfoundry/identity/uaa/util/MediaTypeUtilsTest.java Adds unit coverage to validate ordering, stability, and comparator semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR preserves UAA’s Accept-header content negotiation behavior after Spring Framework removed MediaType.sortByQualityValue() by introducing a local equivalent and updating call sites to use it.

Changes:

  • Added MediaTypeUtils with a Spring 6–compatible quality-value comparator and in-place sort helper.
  • Replaced usages of MediaType.sortByQualityValue(...) with MediaTypeUtils.sortByQualityValue(...).
  • Added unit tests covering null handling and comparator behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeUtils.java New local Spring-6-equivalent comparator + sorting helper for MediaType.
server/src/test/java/org/cloudfoundry/identity/uaa/util/MediaTypeUtilsTest.java New unit tests for the utility comparator/sort behavior.
server/src/main/java/org/cloudfoundry/identity/uaa/web/ConvertingExceptionView.java Switched Accept sorting to use the local utility.
server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/error/DefaultOAuth2ExceptionRenderer.java Switched Accept sorting to use the local utility.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +16 to +64
private MediaTypeUtils() {
}

/**
* Comparator for sorting media types by quality value.
* This implementation preserves the exact sorting logic from Spring Framework 6's
* MediaType.QUALITY_VALUE_COMPARATOR.
*/
public static final Comparator<MediaType> BY_QUALITY_VALUE = (mediaType1, mediaType2) -> {
double quality1 = mediaType1.getQualityValue();
double quality2 = mediaType2.getQualityValue();
int qualityComparison = Double.compare(quality2, quality1);
if (qualityComparison != 0) {
return qualityComparison; // audio/*;q=0.7 < audio/*;q=0.3
} else if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) { // */* < audio/*
return 1;
} else if (mediaType2.isWildcardType() && !mediaType1.isWildcardType()) { // audio/* > */*
return -1;
} else if (!mediaType1.getType().equals(mediaType2.getType())) { // audio/basic == text/html
return 0;
} else { // mediaType1.getType().equals(mediaType2.getType())
if (mediaType1.isWildcardSubtype() && !mediaType2.isWildcardSubtype()) { // audio/* < audio/basic
return 1;
} else if (mediaType2.isWildcardSubtype() && !mediaType1.isWildcardSubtype()) { // audio/basic > audio/*
return -1;
} else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) { // audio/basic == audio/wave
return 0;
} else {
int paramsSize1 = mediaType1.getParameters().size();
int paramsSize2 = mediaType2.getParameters().size();
return Integer.compare(paramsSize2, paramsSize1); // audio/basic;level=1 < audio/basic
}
}
};

/**
* Sorts the given list of {@code MediaType} objects by quality value.
* This method replicates Spring Framework 6's deprecated MediaType.sortByQualityValue()
* behavior.
*
* @param mediaTypes the list to sort
* @throws IllegalArgumentException if the list is {@code null}
*/
public static void sortByQualityValue(List<MediaType> mediaTypes) {
Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
if (mediaTypes.size() > 1) {
mediaTypes.sort(BY_QUALITY_VALUE);
}
}
Comment on lines +21 to +70
@Test
void sortByQualityValueWithNullThrowsException() {
assertThatThrownBy(() -> MediaTypeUtils.sortByQualityValue(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("'mediaTypes' must not be null");
}

@ParameterizedTest
@MethodSource("listsThatShouldNotBeSorted")
void sortByQualityValueWithListsThatDoNotNeedSorting(List<MediaType> mediaTypes, List<MediaType> expected) {
MediaTypeUtils.sortByQualityValue(mediaTypes);
assertThat(mediaTypes).isEqualTo(expected);
}

static Stream<Arguments> listsThatShouldNotBeSorted() {
return Stream.of(
Arguments.of(Collections.emptyList(), Collections.emptyList()),
Arguments.of(Collections.singletonList(MediaType.APPLICATION_JSON), Collections.singletonList(MediaType.APPLICATION_JSON))
);
}

@Test
void sortByQualityValueMaintainsStableSortOrder() {
MediaType audioBasic = new MediaType("audio", "basic");
MediaType audio = new MediaType("audio");
MediaType audio03 = new MediaType("audio", "*", 0.3);
MediaType audio07 = new MediaType("audio", "*", 0.7);
MediaType audioBasicLevel = new MediaType("audio", "basic", Collections.singletonMap("level", "1"));
MediaType all = MediaType.ALL;

List<MediaType> expected = new ArrayList<>();
expected.add(audioBasicLevel);
expected.add(audioBasic);
expected.add(audio);
expected.add(all);
expected.add(audio07);
expected.add(audio03);

List<MediaType> result = new ArrayList<>(expected);
Random rnd = new Random();
// shuffle & sort 10 times
for (int i = 0; i < 10; i++) {
Collections.shuffle(result, rnd);
MediaTypeUtils.sortByQualityValue(result);

for (int j = 0; j < result.size(); j++) {
assertThat(result.get(j)).as("Invalid media type at " + j).isSameAs(expected.get(j));
}
}
}
Comment on lines +42 to +70
@Test
void sortByQualityValueMaintainsStableSortOrder() {
MediaType audioBasic = new MediaType("audio", "basic");
MediaType audio = new MediaType("audio");
MediaType audio03 = new MediaType("audio", "*", 0.3);
MediaType audio07 = new MediaType("audio", "*", 0.7);
MediaType audioBasicLevel = new MediaType("audio", "basic", Collections.singletonMap("level", "1"));
MediaType all = MediaType.ALL;

List<MediaType> expected = new ArrayList<>();
expected.add(audioBasicLevel);
expected.add(audioBasic);
expected.add(audio);
expected.add(all);
expected.add(audio07);
expected.add(audio03);

List<MediaType> result = new ArrayList<>(expected);
Random rnd = new Random();
// shuffle & sort 10 times
for (int i = 0; i < 10; i++) {
Collections.shuffle(result, rnd);
MediaTypeUtils.sortByQualityValue(result);

for (int j = 0; j < result.size(); j++) {
assertThat(result.get(j)).as("Invalid media type at " + j).isSameAs(expected.get(j));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

3 participants