spring-boot

Java 8 and LocalDate serialization issues with Jackson mapper

Say you have a LocalDate attribute in one of your models in your Spring based RESTful Web Service and you need your clients to send and receive the LocalDate in some form of yyyyMMdd
LocalDate myDate;

Simply add the following dependency to your classpath (pom.xml)

   com.fasterxml.jackson.datatype
   jackson-datatype-jsr310
   2.8.6

And annotate your LocalDate attributes as

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyMMdd")
LocalDate myDate;

But what if your project also uses older Date Api? (Say you are using both the original Date and new LocalDate)

Then you might have something like this to set the date format universally across the project to convert all Date instances

@Bean
 public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
     MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
     ObjectMapper objectMapper = new ObjectMapper();
     DateFormat df = new SimpleDateFormat("yyyyMMdd");
     df.setTimeZone(TimeZone.getTimeZone("EST"));
     objectMapper.setDateFormat(df);
     jsonConverter.setObjectMapper(objectMapper);
     return jsonConverter;
 }

Jackson is still on JDK 6. So they have begun a modular plugin approach to support Java 8 features such as the new Date API (LocalDate) etc. To make Jackson play nice with both the old and the new APIs, simply update the above to

@Bean
 public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
     MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
     ObjectMapper objectMapper = new ObjectMapper()
           .registerModule(new ParameterNamesModule())
           .registerModule(new Jdk8Module())
           .registerModule(new JavaTimeModule());
     DateFormat df = new SimpleDateFormat("yyyyMMdd");
     df.setTimeZone(TimeZone.getTimeZone("EST"));
     objectMapper.setDateFormat(df);
     jsonConverter.setObjectMapper(objectMapper);
     return jsonConverter;
 }

Make sure that you also update your pom.xml with the necessary dependencies

   com.fasterxml.jackson.module
   jackson-module-parameter-names


   com.fasterxml.jackson.datatype
   jackson-datatype-jdk8


   com.fasterxml.jackson.datatype
   jackson-datatype-jsr310
   2.8.6

The first dependency in the list adds support for JDK8 datatypes before core Jackson can support them.

Dealing with JPA Entities backed by SQL Queries that return no unique columns

Earlier I was working on a Spring Boot application where in I had to join multiple tables and map the returned columns to a JPA entity. But the problem was that none of the returned columns had unique values.

Here’s a snippet of how my JpaRepository looks like  (I put … to hide the parts of the query that are not relevant to this post)

@Repository
public interface UserJpaRepository extends JpaRepository {

    @Query(value="SELECT a.date_id as date_id...
           "FROM user a, address b, account c " +
            "WHERE ... a.id=?1",nativeQuery = true)
    List<User> getUserById(Long id);

}

But the way these three tables (user, address, account) are setup and the way the query is
structured, none of the columns returned had unique values in them. So I cannot annotate
any attribute in the User Entity with @id annotation. The getUserId(id) would always
return erroneous records. One approachto solve this is by adding newId() to be returned
as a column which is a randomly generated value for each returned row.

@Repository
public interface UserJpaRepository extends JpaRepository {

    @Query(value="SELECT
newId() AS col_id a.date_id as date_id...
            "FROM user a, address b, account c " +
            "WHERE ... a.id=?1",nativeQuery = true)
    List<User> getUserById(Long id);

}

So by adding colId to User and marking it as @id, the above method will return the results
properly.

Note that newId() method is available in MS SQL server. Every other standard RDMS provides
a way to do this.

Global Exception handling for Spring MVC

Proper exception handling in a REST service can help
  • Provide proper HTTP response codes and pertinent error messages to downstream clients
  • Logging custom exceptions can help with analytics (If logs are aggregated through a Log aggregation system such as Splunk, one can easily find out outcomes such as the number of times a specific error has occurred etc.)
Here’s one of many ways to raise custom exceptions and handle them globally using Spring @ControllerAsdvise
Here’s a REST URL mapping that retrieves a User resource by id in this controller
 
 @GetMapping(“/user/{id}”)
    public ResponseEntity<?> getUser(@PathVariable Long id) throws NoMatchingUserException {
        return ResponseEntity.ok(userService.findById(id));
    }
The method throws a custom NoMatchingUserException which can be found here
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = “No such User”)
public class NoMatchingUserException extends UserGenericException {
    public NoMatchingUserException(String message) {
        super(message);
        this.setHttpStatus(HttpStatus.NOT_FOUND);
    }
}
Now let’s look at the Servicemethod that is called by the Controller
    @Override
    public User findById(Long id) throws NoMatchingUserException {
        User user = repository.findById(id);
        if (user == null)
            throw new NoMatchingUserException(“No user found with id “ + id);
        return user;
    }
Line 37 throws the custom NoMatchingUserException with a customized error message.
The above configuration cleanly returns a useful error message to the client during such error condition
Here’s a snapshot of a successful response (When calling http://localhost:8080/user/1)
{id“: 1,firstName“: Jack,lastName“: Bauer}
Here’s a snapshot of an erroneous request (user ID 6 is not found in the system) and its corresponding response (When calling http://localhost:8080/user/6)
{ERROR“: No user found for id 6}
 
If you look in to the network tab of your browser console (Chrome DEV tools -> Network) you can also see that the response error code is properly set to 404.
This recipe can be expanded and generalized further. May be the custom exceptions hold the HTTP status code pertinent to the error condition and bubble it up for the Exceptionhandler to retrieve it and set the response error code. In such scenario, we can have one global generic Exceptionhandler which simplifies the solution further.
The source code for the above can be found here. Simply clone the repository and run the Application.java from your IDE to bootstrap the application.