-
Notifications
You must be signed in to change notification settings - Fork 824
Description
Problem Description
When using PageJacksonModule from Spring Cloud OpenFeign to deserialize org.springframework.data.domain.Page<T> responses, deserialization fails if the application uses a global Jackson PropertyNamingStrategies.SNAKE_CASE configuration.
Example of global configuration:
- Via builder:
JsonMapper.builder().propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)- Or via properties:
spring.jackson.property-naming-strategy=SNAKE_CASEThe SimplePageable record uses hardcoded @JsonProperty annotations with camelCase names:
// Current (simplified)
record SimplePageable(
@JsonProperty("pageNumber") int number,
@JsonProperty("pageSize") int size,
@JsonProperty("sort") Sort sort
) { ... }With a global SNAKE_CASE strategy active:
- Jackson prioritizes the naming strategy and looks for
page_number/page_sizein the JSON. - The
@JsonProperty("pageNumber")and@JsonProperty("pageSize")mappings are conflicted. - Fields like
sizedefault to0(invalid). - This causes the exception:
java.lang.IllegalArgumentException: Page size must not be less than one
Thrown from PageRequest.of(...) inside the SimplePageable constructor.
Reproduction with Minimal Example
A minimal reproducible project is available here:
https://github.com/weslyvinicius/bug-fix-page-jackson-module
Key Reproduction Steps
1. Global Jackson config with SNAKE_CASE
@Configuration
public class JsonMapperConfig {
@Bean
@Primary
public JsonMapper jacksonMapper() {
return JsonMapper.builder()
.propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
.addModule(new PageJacksonModule())
.addModule(new SortJacksonModule())
.build();
}
}2. WireMock stub returns paginated JSON in snake_case
{
"content": [ ... ],
"pageable": {
"page_number": 0,
"page_size": 10
},
"total_elements": 10,
"total_pages": 1
}3. Run integration test
@SpringBootTest
@AutoConfigureMockMvc
@EnableWireMock({
@ConfigureWireMock(
name = "person-service",
baseUrlProperties = "http://localhost",
port = 8081 )
})
class PersonControllerTest {
@Autowired
public MockMvc mockMvc;
@Test
void getAllPersons() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/person"))
.andDo(print())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").isArray());
}
}Expected Behavior
The test passes and Page<Person> is correctly deserialized.
Actual Behavior
Deserialization fails during response extraction with:
Page size must not be less than one
Proposed Fix
Add @JsonAlias to accept snake_case variants while keeping camelCase as primary:
static class SimplePageable implements Pageable {
private final PageRequest delegate;
- @JsonProperty("pageNumber") int number,
+ @JsonProperty("pageNumber")
+ @JsonAlias({"page-number", "page_number", "pagenumber", "PageNumber"})
+ int number,
- @JsonProperty("pageSize") int size,
+ @JsonProperty("pageSize")
+ @JsonAlias({"page-size", "page_size", "pagesize", "PageSize"})
+ int size,
@JsonProperty("sort") Sort sort
) { ... }Optionally, add more aliases for consistency with other fields.
Demonstration of Fix in the Example Repo
In the linked repo, a CustomPageJacksonModule was created by copying/extending the original PageJacksonModule and adding @JsonAlias to SimplePageable.
To verify:
- Comment out:
.addModule(new PageJacksonModule())- Uncomment:
.addModule(new CustomPageJacksonModule())- Rerun the test — it passes successfully.
Benefits
- Fully backward compatible.
- Allows global SNAKE_CASE without custom mappers or per-client overrides.
- Supports common snake_case paginated APIs.
- Avoids forcing camelCase changes in external or legacy APIs.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status