3

I have a controller: (as per Spring WebMVC @ModelAttribute parameter-style)

@GetMapping("/time/{date}")
@ResponseStatus(OK)
public LocalDate getDate(
        @ModelAttribute("date") LocalDate date
) {
    return date;
}

LocalDateFormatter encodes LocalDates from strings "now", "today" and typical "yyyy-MM-dd"-formatted strings, and decodes dates back to strings

public class LocalDateFormatter implements Formatter<LocalDate> {}

I have tested this controller via Spring Test. The test PASSES.

I set up a conversion service and mocked an MVC with it:

var conversion = new DefaultFormattingConversionService();
conversion.addFormatterForFieldType(LocalDate.class, new LocalDateFormatter());

mockMvc = MockMvcBuilders
        .standaloneSetup(TimeController.class)
        .setConversionService(conversionRegistry)
        .build();

Test is parameterized and looks like this:

@ParameterizedTest
@MethodSource("args")
void getDate(String rawDate, boolean shouldConvert) throws Exception {
    var getTime = mockMvc.perform(get("/time/" + rawDate));

    if (shouldConvert) {
        // Date is successfully parsed and some JSON is returned
        getTime.andExpect(content().contentType(APPLICATION_JSON_UTF8));
    } else {
        // Unsupported rawDate
        getTime.andExpect(status().is(400));
    }
}

Here are the parameters:

private static Stream<Arguments> args() {
    // true if string should be parsed
    return Stream.of(
            Arguments.of("now", true),
            Arguments.of("today", true),
            Arguments.of("thisOneShouldNotWork", false),
            Arguments.of("2014-11-27", true)
    );
}

As I've said, test passes.

But when launched from browser, a 400 error is received on ANY request.

How I've tried integrating conversion into Spring MVC (none of this worked):

  • Overriding WebMvcConfigurer's method:

    public class ServletConfig implements WebMvcConfigurer {
        @Override
        public void addFormatters(FormatterRegistry registry) {
            registry.addFormatter(new LocalDateFormatter());
            // ALSO TRIED
            registry.addFormatterForFieldType(LocalDate.class, new LocalDateFormatter());
        }
    }
    
  • Registering a FormattingConversionService

    @Bean
    public FormattingConversionService conversionService() {
        var service = new FormattingConversionService();
        service.addFormatter(new LocalDateFormatter());
        return service;
    }
    

Can someone tell me what's wrong?

P.S. I'm aware that this is not the best way to work with dates, but since it says in Spring reference that this should work, I wanted to try it out.

1 Answers1

3

Define this bean for spring boot:

@Bean
    public Formatter<LocalDate> localDateFormatter() {
        return new Formatter<LocalDate>() {
            @Override
            public LocalDate parse(String text, Locale locale) throws ParseException {
                if ("now".equals(text))
                    return LocalDate.now();
                return LocalDate.parse(text, DateTimeFormatter.ISO_DATE);
            }

            @Override
            public String print(LocalDate object, Locale locale) {
                return DateTimeFormatter.ISO_DATE.format(object);
            }
        };
    }

if you use Spring MVC define like this:

@Configuration
@ComponentScan
@EnableWebMvc
public class ServletConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new Formatter<LocalDate>() {
            @Override
            public LocalDate parse(String text, Locale locale) throws ParseException {
                if ("now".equals(text))
                    return LocalDate.now();
                return LocalDate.parse(text, DateTimeFormatter.ISO_DATE);
            }

            @Override
            public String print(LocalDate object, Locale locale) {
                return DateTimeFormatter.ISO_DATE.format(object);
            }
        });
    }
}

Don't forget implement today function as parameter.

Maxim Kasyanov
  • 938
  • 5
  • 14
  • Could you clarify spring mvc version. I've tested it on 5.0.7 – Maxim Kasyanov Jul 11 '18 at 17:43
  • Same version for me – Denis Babochenko Jul 11 '18 at 17:45
  • Please, attach full config file where you declared this bean – Maxim Kasyanov Jul 11 '18 at 17:48
  • @NEGRKITAEC Is your project private or public? could you upload it to github and share the link? – Maxim Kasyanov Jul 11 '18 at 17:57
  • [GitHub link](https://github.com/stasmihailov/startup_idea_1) It's a learning project. The config with bean is in `ru/tsc/bda/app/webapi/ServletConfig.java`. And sorry for java10 – Denis Babochenko Jul 11 '18 at 18:02
  • Worked only if add additional annotation for parameter. @DateTimeFormat(pattern = "yyyy-MM-dd") @ModelAttribute("date") LocalDate date – Maxim Kasyanov Jul 11 '18 at 18:24
  • this is probably because of default registered converter. But thanks – Denis Babochenko Jul 11 '18 at 18:44
  • @NEGRKITAEC i noticed that ServletConfig doesn't invoke. Try to put breakepoint at any line, try don't stop. because of it formatter don't register – Maxim Kasyanov Jul 11 '18 at 18:58
  • @NEGRKITAEC OOHHH Maaan, You haven't added EnableWebMvc annotation to ServletConfig. Due of that Servlet config never was instantiate. Add annotation and be happy. – Maxim Kasyanov Jul 11 '18 at 19:06
  • @NEGRKITAEC Don't mix up xml and java configuration. Make choise and use one of them. Due of you mix up xml config with java for initialization of webMvc context must be EnableWebMvc annotation, but you used ImportResource("classpath*:spring/servlet/**/*.xml") and in xml wrote . Because of it, controllers works, but WebMvcConfigurer was igroned. All what you need is delete ImportResource("classpath*:spring/servlet/**/*.xml"), also xml file, and add EnableWebMvc. i will change my answer for your case – Maxim Kasyanov Jul 11 '18 at 19:13
  • its done. Many thanks! And xml config was there because where I work xml is used at some older projects for legacy issues. – Denis Babochenko Jul 11 '18 at 19:58