0

I'm having trouble integrating Spring with Jackson.

I have a POJO which has some Instant fields with a custom date format:

public class C{ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss", timezone = "UTC") private Instant postedAtFrom; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss", timezone = "UTC") private Instant postedAtTo; } 

Then I'm trying to use this POJO as a query parameter for a rest GET mapping:

@RestController @RequestMapping("/c") @RequiredArgsConstructor @Slf4j public class AnnounceController { @Autowired private final ObjectMapper objectMapper; @PostConstruct public void test() throws JsonProcessingException { System.out.println(objectMapper.readValue("{\"postedAtFrom\":\"20-05-2022 15:50:07\"}", C.class)); } @GetMapping public BasicResponse<Page<Announce>> get(final C filter, @NonNull final Pageable page) { return new BasicResponse<>(service.get(filter, page)); } } 

The PostConstruct code works fine and it prints exactly what it should print, but whenever I try to make a get to that endpoint with the very same request:

CURL GET /c?postedAtFrom=20-05-2022 15:50:07 HTTP/1.1 Host: localhost 

I get this exception:

org.springframework.validation.BeanPropertyBindingResult: 1 errors Field error in object 'filter' on field 'postedAtFrom': rejected value [20-05-2022 15:50:07] codes [typeMismatch.filter.postedAtFrom,typeMismatch.postedAtFrom,typeMismatch.java.time.Instant,typeMismatch] arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [filter.postedAtFrom,postedAtFrom] arguments [] default message [postedAtFrom]] default message [Failed to convert property value of type 'java.lang.String' to required type 'java.time.Instant' for property 'postedAtFrom' nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@com.fasterxml.jackson.annotation.JsonFormat java.time.Instant] for value [20-05-2022 15:50:07] nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [20-05-2022 15:50:07]] 

Here is the code for the ObjectMapper bean:

@Bean @Primary public ObjectMapper configure(@NonNull final ObjectMapper objectMapper) { final SimpleModule javaTimeModule = new JavaTimeModule() .addDeserializer(Instant.class, InstantDeserializer.INSTANT) .addSerializer(Instant.class, InstantSerializer.INSTANCE); return objectMapper.registerModule(javaTimeModule); } 

I would like to to resolve this using the designed tools of Spring and Jackson. Please do not suggest a workaround like "convert it to a string" or "make a post".

8
  • 1
    Why doesn't the linked post work? Do you have an appropriate converter? Please provide a minimal reproducible example. Edit your post to explain where that solution is blocked (if that's the case). Commented Sep 11, 2024 at 13:30
  • I couldn't reproduce this issue. Perhaps create a minimal, self-contained, reproducible example? This works perfectly: jdoodle.com/ia/1gCC - it's just pure Jackson, no Spring. Try it, and if it works, then the problem is caused by Spring messing Jackson up. Commented Sep 11, 2024 at 14:24
  • Actually, the example I previously linked is probably irrelevant: the error message comes directly from Spring, nothing in there about Jackson, and it looks like Spring hasn't used Jackson or anything at this point. So it sounds like you need a converter as Sotirios (and the linked dupe) suggested. Commented Sep 11, 2024 at 14:36
  • 1
    That's another question, do you expect your CURL client to be sending JSON? Why? Your C is currently un-annotated, which means that Spring MVC will assume it's a model attribute and try to parse the query params to produce an instance. There's no JSON parsing involved at all. Commented Sep 11, 2024 at 14:45
  • 1
    When you disagree with a duplicate, the correct approach is to edit your post to explain why. Users can then vote to reopen. I would've voted myself. No one is out to get you. Commented Sep 12, 2024 at 14:28

2 Answers 2

2

As others have stated in the comments, it's not very clear what you're expecting to happen. I make assumptions below.

Considering the @JsonFormat annotation and the custom ObjectMapper, if you're expecting Jackson to kick in here and parse JSON, that's not going to happen. To be clear, no Jackson nor JSON parsing is involved anywhere in here.

Your final C filter handler method parameter isn't annotated with something like @RequestBody (and you're not sending a request body in that GET Curl call). Without an annotation, Spring MVC falls back to handling the parameter as a model attribute and processes it with ModelAttributeMethodProcessor.

It creates an instance of your type C and attempts to populate its fields with query (or form- if that's what you had used) parameters. In this case, it'll look for a query parameter named postedAtFrom to match the field (really the bean/pojo accessor, which I also assume you have).

The processor uses a set of Converter instances to populate the fields from String values (the query parameters) into the complex types your model attribute type is likely using.

Spring Boot maintains its own list of default Converter instances. There's one in there for java.time types, Jsr310DateTimeFormatAnnotationFormatterFactory (this isn't the Converter, but it's what the Converter uses), which is set up to handle String to Instant conversion.

However, your query parameter value, 20-05-2022 15:50:07, is not going to be parseable as an Instant, there's information about the Instant missing (timezone for one). In certain, you'd be able to annotate your field @DateTimeFormat to specify a custom format, but I don't think that'll work here.

You can instead change your field to be a LocalDateTime

@DateTimeFormat(pattern = "dd-MM-yyyy HH:mm:ss") private LocalDateTime postedAtFrom; 

with an appropriate pattern matching your input. And, whenever you need an Instant, use LocalDateTime's toInstant method with an appropriate ZoneOffset.

Alternatively, you can configure Spring Boot with your own list of Converter instances that can parse directly to an Instant with your own DateTimeFormatter. See this post for information on how to do that.

Sign up to request clarification or add additional context in comments.

1 Comment

this is a great answer, i didn't had time to test yet but it sounds good... what i want is exactly what you described, parse the GET query params into a pojo entity, which i assumed was done by jackson as well [i believe im not the first to make this assumption] it is not intended to be a POST to be more rest respectful
1

@Savior's answer looked very good but because I didn't really know how a new date converter would mingle among the others spring already has I went to a less elegant but easier solution. I added a set method in my POJO

 @DateTimeFormat(pattern = "dd-MM-yyyy HH:mm:ss") public void setPostedAtFrom(final String date) { this.postedAtFrom = LocalDateTime.parse(date, SIMPLE_DATE_FORMAT).atZone(ZoneId.systemDefault()).toInstant(); } @DateTimeFormat(pattern = "dd-MM-yyyy HH:mm:ss") public void setPostedAtTo(final String date) { this.postedAtTo = LocalDateTime.parse(date, SIMPLE_DATE_FORMAT).atZone(ZoneId.systemDefault()).toInstant(); } private static final DateTimeFormatter SIMPLE_DATE_FORMAT = new DateTimeFormatterBuilder() .appendPattern("dd-MM-yyyy HH:mm:ss") .toFormatter(); 

the @DateTimeFormat is really irrelevant here but helps me to remember what is the format that string is supposed to come [this is a really big pojo]

1 Comment

SimpleDateFormat is not thread safe? one would imagine that make it thread safe would pretty obvious... I cant change the client or date format and as the application has no possibility of going international timezone is never a concern

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.