restspring-cloud-feignlocaldate

FeignClient is changing the format of LocalDate passed to it


I'm have a @FeignClient in my app:

@FeignClient(name="${mongo.service.id}", url="${mongo.service.url}")
public interface MongoCustomerClaimInterface {
    @GetMapping(path = "/api/customerClaim/countClaims/{businessDate}")
    List<TransactionClaimStatusData> countClaimsByStatusToBusinessDate(
    @PathVariable @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) 
    LocalDate businessDate);
}

I call the feign method and passing it a formatted LocalDate variable, and printing it to the log:

LocalDate businessDate = getBusinessDate();
LocalDate formattedDate = LocalDate.parse(businessDate.toString(), 
                                          DateTimeFormatter.ISO_DATE);
log.info("formattedDate: " + formattedDate);
claimStatusDataList = mongoCustomerClaimInterface.countClaims(formattedDate);

The call generates 404 error and log:

2020-24-02 18:10:25.433 INFO  DashboardServiceImpl - formattedDate: 2020-02-23
2020-24-02 18:10:25.440 DEBUG
RequestMappingHandlerMapping:
Looking up handler method for path /api/customerClaim/countClaims/2/23/20
RequestMappingHandlerMapping:
Did not find handler method for [/api/customerClaim/countClaims/2/23/20]

Although I pass a date in the format yyyy-mm-dd so it will match:

How can I prevent Feign from doing this and configure a uniform formatter?


Solution

  • So apparently Feign isn't working with all of SpringMvc annotations. @DateTimeFormat, as great as it is, is a SpringMvc annotation and NOT a FeignClient annotation. I solved this by doing several things:

    1. Created a MessageConvertersConfiguration class in my MvcConfig class. In it, I created a LocalDate & LocalDateTime converter bean and added it to the converters list that this configurer uses when an http message arrives:
    @Configuration
    public class MvcConfig {
        
        @Configuration
        @EnableWebMvc
        public class MessageConvertersConfiguration implements WebMvcConfigurer {
            @Override
            public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
              converters.add(new MappingJackson2HttpMessageConverter(localDateTimeConverter()));
            }
    
            @Bean
            public ObjectMapper localDateTimeConverter() {
                ObjectMapper mapper = new ObjectMapper();
                SimpleModule localDateTimeModule = new SimpleModule();
    
                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy'T'HH:mm:ss");
                localDateTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
                localDateTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
    
                DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
                localDateTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
                localDateTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
    
                mapper.registerModules(localDateTimeModule);
    
                return mapper;
            }
        }
    }
    
    1. Created a configuration class for my feign client. In it, I instantiated a SpringMvcContract. Because Feign is created before SpringMvc, the converters we just defined won't affect feign without this contract:
    @Configuration
    public class FeignConfig {
        @Bean
        public Contract feignContract() {
            return new SpringMvcContract();
        }
    }
    
    1. Eventually, I added to configuration attribute to my FeignClient:
    @FeignClient(name="${mongo.service.id}", url="${mongo.service.url}", configuration = FeignConfig.class)
    public interface MongoCustomerClaimInterface {
        @GetMapping(path = "/api/customerClaim/countClaimsByStatusToBusinessDate/{businessDate}")
        List<TransactionClaimStatusData> countClaimsByStatusToBusinessDate(@PathVariable @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate businessDate);
    }