Spring MVC support for java.util.Optional
Java introduced java.util.Optional in Java 8. The introduction of an option type may be new to some Java developers but the idea has been around in languages like Scala and Haskell for a while now. Optional works as a container object that forces the consumer to handle the presence or absence of the contained value. Optionals can hopefully relegate NullPointerExceptions to a thing of the past.
Spring MVC added support for Optional in 4.1. This enabled request parameters, request headers, matrix variables, and path variables to be bound to an Optional. The following examples will demonstrate how to use Optional with request parameters.
Optional Request Parameter
Annotated Controller
The following example is an annotated controller that listens for a ‘/hello’ request. The endpoint defines the name request parameter, so if ‘Foo’ is supplied as the name, then the controller will return ‘Hello Foo!’. The name request parameter is also defined as not required, so if the name is absent the controller will return ‘Hello null!’.
@Controller
public class HelloController {
@RequestMapping(value = "/hello", produces = "text/plain")
@ResponseBody
private String hello(@RequestParam(required = false) final String) {
return String.format("Hello %s!", name);
}
}
Annotated Controller Using Optionals
The following example is similar to the previous example, but the name request parameter is defined as a java.util.Optional. The Optional forces the consumer to handle the case when the name request parameter may be absent. In this case, a missing name will result in ‘Hello World!’.
@Controller
public class HelloController {
@RequestMapping(value = "/hello", produces = "text/plain")
@ResponseBody
private String hello(@RequestParam(required = false, value = "name") final Optional<String> name) {
final String helloName = name.orElse("World")
return String.format("Hello %s!", helloName);
}
}
Testing Strategy
The following class will build a simple web context to support testing the controller endpoint using a MockMvc.
The test class uses the @EnableWebMvc annotation to load the DefaultFormattingConversionService. Without the DefaultFormattingConversionService, Spring will not be able to use the ObjectToOptionalConverter to convert a request parameter into an Optional.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class HelloControllerTest {
@Autowired
WebApplicationContext wac;
private MockMvc mockMvc;
@Configuration
@EnableWebMvc // Loads the DefaultFormattingConversionService to support binding to Optionals (ObjectToOptionalConverter)
@ComponentScan(basePackages = "com.rseanking")
public static class HelloControllerConfiguration {
}
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
...
}
Handle Request without Request Parameter
The following test sends a GET request to the /hello URL without a name request parameter and verifies the endpoint returns a 200 status and ‘Hello World!’.
@Test
public void shouldHandleRequetWithoutName() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("Hello World!"));
}
Handle Request with Request Parameter
The following test sends a GET request to the /hello URL with a name request parameter of ‘Foo’ and verifies the endpoint returns a 200 status and ‘Hello Foo!’.
@Test
public void shouldHandleRequetWithName() throws Exception {
mockMvc.perform(get("/hello").param("name", "Foo"))
.andExpect(status().isOk())
.andExpect(content().string("Hello Foo!"));
}
Conclusion
Java’s implementation of an optional container isn’t perfect, but it’s start in the right direction. While the API was developed for returned types, it is nice that Spring provides support for Optional. Using Optional will force developers to handle the absence of a request parameter, request header, matrix variable, or path variable, instead of experiencing unexpected behaviors like NullPointerExceptions.