javaspring-bootjacksonspring-resttemplate

JsonFormat$Value.hasLenient() Error on Rest Call


I'm building a set of microservices in Spring Boot. I've got one service which interfaces with the database and has a series of RESTful endpoints which other apps can hit. Many of these table entities are complex objects and are served up as a JSON object but some are simple parameters and so are served as strings. When I hit the main DB service endpoints, all data is returned fine without issue.

I have a series of other applications which consume this data in various ways. Because many objects are reused across applications (the DB service response shape, the person entity POJO, etc), I've got a package made with some common classes. Each app imports these common classes and then makes calls to the main DB service via RestTemplate. Because the response is always wrapped in a standard way, I am getting the values by performing an exchange which expects a ParameterizedTypeReference of the response wrapper with the appropriate POJO or Java variable (String, int, etc).

As of 5 months ago, all was good. The app was deployed to a server and is still operating normally. However, I recently went back in to the code in order to make some modifications and now I find that when I run the app locally some of the endpoints fail with the following error:

java.lang.NoSuchMethodError: com.fasterxml.jackson.annotation.JsonFormat$Value.hasLenient()Z

I thought at first that perhaps the two POMs were out of sync and one was pulling a newer version of Jackson but after hard coding the version in the POM to be 100% certain they were the same (version 2.10.1), it still has the same issue. It should also be noted that the method in the error message appears to have been added in version 2.9 and has not been deprecated as of this moment in time. This error only happens when trying to retrieve data from endpoints returning POJOs and not when the data type is a String. It may also be relevant that the call appears to never actually leave the app before throwing an error as the DB service endpoint never gets hit.

Any ideas why I'm getting this error and how I could resolve it?

Response Object:

public class DatabaseServiceResponse<T> {
    @JsonInclude(Include.NON_NULL)
    private List<T> entities;
    
    @JsonInclude(Include.NON_NULL)
    private String error;
    
    [getters and setters ommitted]
}

Person Service:

public List<Person> getPeople() {
    List<Person> people = new ArrayList<>();
    HttpHeaders headers = getHeaders();
    HttpEntity<Object> requestEntity = new HttpEntity<>(null, headers);
    ResponseEntity<DatabaseServiceResponse<Person>> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, new ParamaterizedTypeReference<DatabaseServiceResponse<Person>>() {});  // Fails on this line
    if (response.getStatusCode() == HttpStatus.OK) {
        people = response.getBody().getEntities();
    } else {
        throw new ServiceException("Error retrieving people");
    }
    return people;
}

public List<String> getPersonTypes() {
    List<String> personTypes = new ArrayList<>();
    HttpHeaders headers = getHeaders();
    HttpEntity<Object> requestEntity = new HttpEntity<>(null, headers);
    ResponseEntity<DatabaseServiceResponse<String>> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, new ParamaterizedTypeReference<DatabaseServiceResponse<String>>() {});  // Works fine here
    if (response.getStatusCode() == HttpStatus.OK) {
        personTypes = response.getBody().getEntities();
    } else {
        throw new ServiceException("Error retrieving person types");
    }
    return personTypes;
}

Person App POM:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath />
</parent>

Common Elements POM:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.10.1</version>
    <scope>provided</scope>
</dependency>

UPDATE I believe the external library and the potentially out-of-sync POM files was a red herring. I have copied all of the relevant classes into the main project to remove the dependency on the external package and I have the same issue. I've also tried completely upgrading all versions to the latest and still no luck. Jackson, for whatever reason, will map JSON to a String or a list of Strings but just will not map my JSON to a POJO.


Solution

  • I finally worked out what was wrong here and it feels so absurd that I don't believe anyone would ever have gotten to the right answer without the code in their hands. It was only by pulling all the code back into one place and then seeking to find another means of mapping the object via Jackson (using an ObjectMapper directly as opposed to letting the RestTemplate figure that out) that I stumbled upon the true problem.

    There is an audit class which is a property on my Person object which contains the normal, basic properties of created by, created date, modified by, modified date. These dates are LocalDateTime objects in the standard ISO-8601 format. It turns out that Jackson can no longer process the string straight into the LocalDateTime object and needs "help". By adding a setter which takes the date as a string and does a LocalDateTime.parse(date) to set the actual property, I'm now able to successfully hit the endpoints at will.

    I still have no clue why the exact error above was thrown, but there you have it. By not specifying a way to help translate from a String to the LocalDateTime object, I received the JsonFormat$Value.hasLenient() error from Jackson.