Environment
| Dependency |
Version |
| Spring Boot |
4.0.6 |
Spring Cloud OpenFeign (spring-cloud-openfeign-core) |
5.0.0 |
| Spring Framework |
7.0.7 |
io.github.openfeign:feign-core |
13.6 |
tools.jackson.core:jackson-databind |
3.1.2 |
tools.jackson.datatype:jackson-datatype-javax-money |
3.0.0 |
javax.money:money-api |
1.1 |
org.javamoney.moneta:moneta-core |
1.4.4 |
| JDK |
OpenJDK 25.0.2 (Zulu) |
| OS |
Ubuntu 24.04.2 LTS (kernel 6.17.0-29) |
Steps to reproduce
- Create a Spring Boot 4 project with
spring-boot-starter-web and spring-cloud-starter-openfeign.
- Add
javax.money:money-api, moneta-core, and tools.jackson.datatype:jackson-datatype-javax-money.
- Register
JavaxMoneyModule as a @Bean:
@Bean
public JavaxMoneyModule moneyJacksonModule() {
return new JavaxMoneyModule();
}
- Create a DTO with a
MonetaryAmount field and a @RestController that returns it.
- Create a
@FeignClient pointing at that endpoint.
- Call the Feign client from a test.
Expected behavior
MonetaryAmount is deserialized correctly. JavaxMoneyModule is registered as a @Bean; Spring Boot's Jackson auto-configuration picks it up and includes it in the primary
JsonMapper. The Feign client's converter should use the same JsonMapper.
Actual behavior
tools.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `javax.money.MonetaryAmount`
(no Creators, like default constructor, exist): abstract types either need to be
mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: REDACTED]; (through reference chain: com.example.dto.MoneyDto["amount"])
The @RestController in the same application context serializes MonetaryAmount correctly — only the Feign client's deserializer is affected.
Root cause analysis
Feign builds its JacksonJsonHttpMessageConverter in two phases.
Phase 1 — builder phase. JacksonHttpMessageConvertersConfiguration (@Order(0)) calls builder.withJsonConverter(...) and stores the converter in the builder's core slot.
Attempting to override it via a second ClientHttpMessageConvertersCustomizer with @Order(1) is unreliable: @Order on a @Bean method returning a lambda is not preserved on the
lambda instance itself, so orderedStream() does not guarantee ordering — the @Order(0) customizer may run last and overwrite any fix.
Phase 2 — list phase. HttpMessageConverterCustomizer (Spring Cloud OpenFeign) is applied after builder.build() to the final List<HttpMessageConverter<?>>. By this point the
full Spring context is up and the JsonMapper singleton contains all registered modules including JavaxMoneyModule.
The converter installed in phase 1 does not include JavaxMoneyModule, and no phase-2 customizer is provided automatically for money types — so Feign's deserializer never receives the
module.
Workaround
Register an HttpMessageConverterCustomizer bean that replaces the phase-1 converter with one backed by the correctly configured JsonMapper:
@Bean
HttpMessageConverterCustomizer feignJacksonMoneyConverterCustomizer(JsonMapper jsonMapper) {
return converters -> {
converters.removeIf(c -> c == null || c instanceof JacksonJsonHttpMessageConverter);
converters.add(new JacksonJsonHttpMessageConverter(jsonMapper));
};
}
Minimal reproduction
sb4-issue-money
Two tests demonstrate the difference:
BugReproTest — fails without the workaround (JavaxMoneyModule registered as @Bean, no customizer)
WorkaroundTest — passes with HttpMessageConverterCustomizer
Environment
spring-cloud-openfeign-core)io.github.openfeign:feign-coretools.jackson.core:jackson-databindtools.jackson.datatype:jackson-datatype-javax-moneyjavax.money:money-apiorg.javamoney.moneta:moneta-coreSteps to reproduce
spring-boot-starter-webandspring-cloud-starter-openfeign.javax.money:money-api,moneta-core, andtools.jackson.datatype:jackson-datatype-javax-money.JavaxMoneyModuleas a@Bean:MonetaryAmountfield and a@RestControllerthat returns it.@FeignClientpointing at that endpoint.Expected behavior
MonetaryAmountis deserialized correctly.JavaxMoneyModuleis registered as a@Bean; Spring Boot's Jackson auto-configuration picks it up and includes it in the primaryJsonMapper. The Feign client's converter should use the sameJsonMapper.Actual behavior
The
@RestControllerin the same application context serializesMonetaryAmountcorrectly — only the Feign client's deserializer is affected.Root cause analysis
Feign builds its
JacksonJsonHttpMessageConverterin two phases.Phase 1 — builder phase.
JacksonHttpMessageConvertersConfiguration(@Order(0)) callsbuilder.withJsonConverter(...)and stores the converter in the builder's core slot.Attempting to override it via a second
ClientHttpMessageConvertersCustomizerwith@Order(1)is unreliable:@Orderon a@Beanmethod returning a lambda is not preserved on thelambda instance itself, so
orderedStream()does not guarantee ordering — the@Order(0)customizer may run last and overwrite any fix.Phase 2 — list phase.
HttpMessageConverterCustomizer(Spring Cloud OpenFeign) is applied afterbuilder.build()to the finalList<HttpMessageConverter<?>>. By this point thefull Spring context is up and the
JsonMappersingleton contains all registered modules includingJavaxMoneyModule.The converter installed in phase 1 does not include
JavaxMoneyModule, and no phase-2 customizer is provided automatically for money types — so Feign's deserializer never receives themodule.
Workaround
Register an
HttpMessageConverterCustomizerbean that replaces the phase-1 converter with one backed by the correctly configuredJsonMapper:Minimal reproduction
sb4-issue-money
Two tests demonstrate the difference:
BugReproTest— fails without the workaround (JavaxMoneyModuleregistered as@Bean, no customizer)WorkaroundTest— passes withHttpMessageConverterCustomizer