Spring MVC: Failed to convert value of type ‘java.lang.String’ to required type ‘java.time.LocalDateTime’

PROBLEM

Given the following controller …

@RestController
@RequestMapping(value = '/controller')
class MyController {

    @RequestMapping(method = RequestMethod.GET)
    ResponseEntity main(@RequestParam(name = 'dateTime') LocalDateTime dateTime) {
        // ...

        return ResponseEntity.noContent().build()
    }
}

When executing …

GET https://localhost:8443/controller?dateTime=2017-06-22T17:38

… the web service call returns 400 Bad Request with the following error in the console log:-

Failed to bind request element: org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: 
Failed to convert value of type 'java.lang.String' to required type 'java.time.LocalDateTime'; nested exception 
is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] 
to type [@org.springframework.web.bind.annotation.RequestParam java.time.LocalDateTime] for value '2017-06-22T17:38'; 
nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2017-06-22T17:38]

SOLUTION

One solution is to change the data type from java.time.LocalDateTime to java.lang.String before parsing it to java.time.LocalDateTime. However, it is a little more verbose than I like.

A better way is to leverage @DateTimeFormat:-

@RestController
@RequestMapping(value = '/controller')
class MyController {

    @RequestMapping(method = RequestMethod.GET)
    ResponseEntity main(@RequestParam(name = 'dateTime') @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") LocalDateTime dateTime) {
        // ...

        return ResponseEntity.noContent().build()
    }
}

Spring MVC: Handling Joda Data Types as JSON

PROBLEM

Let’s assume we have the following bean that contains Joda’s LocalDate and LocalDateTime objects:-

public class MyBean {
    private LocalDate date;
    private LocalDateTime dateTime;

    public LocalDate getDate() {
        return date;
    }

    public void setDate(LocalDate date) {
        this.date = date;
    }

    public LocalDateTime getDateTime() {
        return dateTime;
    }

    public void setDateTime(LocalDateTime dateTime) {
        this.dateTime = dateTime;
    }
}

This simple Spring MVC rest controller creates this bean and returns the JSON data back to the client:-

@RequestMapping(value = "/joda", method = RequestMethod.GET)
public ResponseEntity joda() {
    MyBean myBean = new MyBean();
    myBean.setDate(LocalDate.now());
    myBean.setDateTime(LocalDateTime.now());

    return new ResponseEntity<mybean>(myBean, HttpStatus.OK);
}

By default, the generated JSON looks like this:-

{
   "date":
   [
       2015,
       3,
       28
   ],
   "dateTime":
   [
       2015,
       3,
       28,
       18,
       12,
       58,
       992
   ]
}

How do we nicely format these values and still retain the correct data types (LocalDate and LocalDateTime) in MyBean instead of writing our custom formatter and store the values as String?

SOLUTION

First, add a dependency for jackson-datatype-joda.

<dependency>
    <groupid>com.fasterxml.jackson.core</groupid>
    <artifactid>jackson-core</artifactid>
    <version>2.5.1</version>
</dependency>
<dependency>
    <groupid>com.fasterxml.jackson.core</groupid>
    <artifactid>jackson-databind</artifactid>
    <version>2.5.1</version>
</dependency>
<dependency>
    <groupid>com.fasterxml.jackson.core</groupid>
    <artifactid>jackson-annotations</artifactid>
    <version>2.5.1</version>
</dependency>
<dependency>
    <groupid>com.fasterxml.jackson.datatype</groupid>
    <artifactid>jackson-datatype-joda</artifactid>
    <version>2.5.1</version>
</dependency>

Next, instruct MappingJackson2HttpMessageConverter to accept a custom ObjectMapper.

<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean" p:indentoutput="true" p:simpledateformat="yyyy-MM-dd'T'HH:mm:ss.SSSZ">
</bean>

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" p:targetobject-ref="objectMapper" p:targetmethod="registerModule">
    <property name="arguments">
        <list>
            <bean class="com.fasterxml.jackson.datatype.joda.JodaModule">
        </bean></list>
    </property>
</bean>

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
        <bean class="org.springframework.http.converter.ResourceHttpMessageConverter">
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper">
        </property></bean>
    </bean></bean></mvc:message-converters>
</mvc:annotation-driven>

The generated JSON output now looks like this:-

{
   "date": "2015-03-28",
   "dateTime": "2015-03-28T18:11:16.348"
}

Spring Security: Handling 403 Error Page

If you are already using Spring, then you might want to use Spring Security to secure your web resources.

To do that, we specify the URI to be secured with <security:intercept-url/> tag:-

&lt;beans ...&gt;
    &lt;!-- Error pages don't need to be secured --&gt;
    &lt;security:http pattern=&quot;/error/**&quot; security=&quot;none&quot;/&gt;

    &lt;security:http auto-config=&quot;true&quot;&gt;
        &lt;security:form-login ... /&gt;
        &lt;security:logout ... /&gt;
        &lt;security:intercept-url pattern=&quot;/top-secrets/**&quot; access=&quot;ROLE_TOPSECRET&quot;/&gt;
    &lt;/security:http&gt;
		
		...
&lt;/beans&gt;

When users without role ROLE_TOPSECRET access /top-secrets/kfc-secret, they will see this default error page:-

This proves that Spring Security is doing its job. However, the default error page looks rather F.U.G.L.Y. Further, the error page may reveal too much information about the application server. The above error page shows the application runs on Jetty. If I’m a motherhacker, I would research all the possible vulnerabilities on this particular application server in attempt to hack it.

A better solution is to provide a friendly error page when the user access is denied. This can be done by specifying <security:access-denied-handler/> tag:-

&lt;beans ...&gt;
    &lt;!-- Error pages don't need to be secured --&gt;
    &lt;security:http pattern=&quot;/error/**&quot; security=&quot;none&quot;/&gt;

    &lt;security:http auto-config=&quot;true&quot;&gt;
        &lt;security:form-login ... /&gt;
        &lt;security:logout ... /&gt;
        &lt;security:access-denied-handler error-page=&quot;/error/access-denied&quot;/&gt;
        &lt;security:intercept-url pattern=&quot;/top-secrets/**&quot; access=&quot;ROLE_TOPSECRET&quot;/&gt;
    &lt;/security:http&gt;
		
		...
&lt;/beans&gt;

Then, we create a simple error controller that returns the error page:-

@Controller
@RequestMapping(value = &quot;/error&quot;)
public class ErrorController {
    @RequestMapping(value = &quot;/access-denied&quot;, method = RequestMethod.GET)
    public String accessDenied() {
        return &quot;error-access-denied&quot;;
    }
}

Now, the user will see this custom error page:-

This solution is better than the previous one. However, SiteMesh doesn’t have the opportunity to decorate this error page before it gets rendered.

To fix this, we can create a simple redirect to allow the request to make a full-round trip to the server so that SiteMesh can decorate the error page:-

@Controller
@RequestMapping(value = &quot;/error&quot;)
public class ErrorController {
    @RequestMapping(value = &quot;/router&quot;, method = RequestMethod.GET)
    public String errorRouter(@RequestParam(&quot;q&quot;) String resource) {
        return &quot;redirect:/error/&quot; + resource;
    }

    @RequestMapping(value = &quot;/access-denied&quot;, method = RequestMethod.GET)
    public String accessDenied() {
        return &quot;error-access-denied&quot;;
    }
}

Then, we tweak the Spring Security to use the error router URI:-

&lt;beans ...&gt;
    &lt;!-- Error pages don't need to be secured --&gt;
    &lt;security:http pattern=&quot;/error/**&quot; security=&quot;none&quot;/&gt;

    &lt;security:http auto-config=&quot;true&quot;&gt;
        &lt;security:form-login ... /&gt;
        &lt;security:logout ... /&gt;
        &lt;security:access-denied-handler error-page=&quot;/error/router?q=access-denied&quot;/&gt;
        &lt;security:intercept-url pattern=&quot;/top-secrets/**&quot; access=&quot;ROLE_TOPSECRET&quot;/&gt;
    &lt;/security:http&gt;
		
		...
&lt;/beans&gt;

Now, the user will see this nice beautiful error page:-