Java 8 shipped with new language features and libraries and Spring 4.x is already supporting many of these. Some of the new Java 8 features don’t have an impact on Spring and can just be used as is, while other Java 8 features require Spring to explicitly support them. This article will walk you through the new Java 8 features that are supported by Spring 4.0 and 4.1.
Spring 4 supports Java 6, 7, and 8
Code compiled with the Java 8 compiler produces .class files that require a minimum Java 8 Virtual Machine to run. Since Spring makes heavy use of reflection and other byte code manipulation libraries such as ASM, CGLIB and others, it was important to make sure that those libraries were able to understand the new Java 8 class files. Therefore Spring is now embedding ASM, CGLIB and other libraries within the Spring Framework using jar jar (https://code.google.com/p/jarjar/) so that Spring has a predictable set of libraries that can work with Java 6, 7, and 8 byte codes without triggering a runtime error.
The Spring framework itself is compiled using the Java 8 compiler with the command line option to produce Java 6 byte code. Therefore you can compile and run Spring 4.x applications with Java 6, 7 or 8.
Spring and Java 8 Lambda expressions
The designers of Java 8 wanted to ensure that it was backward compatible so that Java 8 lambda expressions can be used with code compiled with previous versions. Backward compatibility was achieved by defining the concept of functional interfaces.
Basically, the Java 8 designers analyzed existing bodies of Java code and noticed that most Java programmers used interfaces with a single method to represent the idea of a function. For example the following is a list of interfaces from the JDK and Spring that only use a single method – these are said to be ‘functional interfaces’.
JDK functional interfaces:
public interface Runnable {
public abstract void run();
}
public interface Comparable<T> {
public int compareTo(T o);
}
Spring framework functional interfaces:
public interface ConnectionCallback<T> {
T doInConnection(Connection con) throws SQLException, DataAccessException;
}
public interface RowMapper<T>
{
T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
In Java 8 a lambda expression can be used anywhere a functional interface is passed into a method or returned from a method.For example the Spring JdbcTemplate class contains a method with the following signature
public <T> List<T> query(String sql, RowMapper<T> rowMapper)
throws DataAccessException
Notice that the second argument of the query method is expecting an instance of the RowMapper interface. With Java 8 we can write a lambda expression as the value for the second argument of the query method.
Instead of writing code like this:
jdbcTemplate.query("SELECT * from products", new RowMapper<Product>(){
@Override
public Product mapRow(ResultSet rs, int rowNum) throws SQLException {
Integer id = rs.getInt("id");
String description = rs.getString("description");
Integer quantity = rs.getInt("quantity");
BigDecimal price = rs.getBigDecimal("price");
Date availability = rs.getDate("available_date");
Product product = new Product();
product.setId(id);
product.setDescription(description);
product.setQuantity(quantity);
product.setPrice(price);
product.setAvailability(availability);
return product;
}
});
we write the code like this:
jdbcTemplate.query("SELECT * from queries.products", (rs, rowNum) -> {
Integer id = rs.getInt("id");
String description = rs.getString("description");
Integer quantity = rs.getInt("quantity");
BigDecimal price = rs.getBigDecimal("price");
Date availability = rs.getDate("available_date");
Product product = new Product();
product.setId(id);
product.setDescription(description);
product.setQuantity(quantity);
product.setPrice(price);
product.setAvailability(availability);
return product;
});
Notice that the Java 8 version of this code is using lambda expressions, which is much more compact and cleaner than the code used in the previous versions using anonymous inner classes.
Covering all the details of Java 8 functional interfaces is beyond the scope of this article and it is highly recommend you learn the details of functional interfaces elsewhere. However the key take away point is that Java 8 lambda expressions can be passed to methods compiled with Java 7 and earlier if the methods accept a functional interface.
The Spring code base contains a lot of functional interfaces, therefore lambda expressions can be easily used with Spring. Even though the Spring framework itself is compiled to Java 6 .class file format you can still write application code using Java 8 lambda expressions, compile it with the Java 8 compiler, and run it on the Java 8 VM and it will just work.
In conclusion, because the Spring Framework has been using functional interfaces before Java 8 formalized the definition of a functional interface it is easy to use lambda expressions with Spring.
Spring 4 and the Java 8 Time and Date API
Java developers have long bemoaned the design flaws of java.util.Date class and, finally, with Java 8 there is a whole new Date and Time API which solves these long standing issues. The new Date and Time API is worthy of its own article therefore we will not cover it in detail other than to note that the new java.time packages introduce many new classes such as LocalDate, LocalTime, and LocalDateTime.
Spring ships with a data conversion framework that is able to convert from strings to Java data types and from Java data types to strings. Spring 4 updates the conversion framework to support all the classes that are part of the Java 8 Date and Time API. Therefore you can write code like this.
@RestController
public class ExampleController {
@RequestMapping("/date/{localDate}")
public String get(@DateTimeFormat(iso = ISO.DATE) LocalDate localDate)
{
return localDate.toString();
}
}
Notice that in the above example the parameter to the get method is of type LocalDate from Java 8 and Spring 4 will be able to take a string such as 2014-02-01 and convert it to an instance of a Java 8 LocalDate
It is important to note that Spring is frequently used with other libraries such as Hibernate for data persistence and Jackson for converting Java objects to / from JSON.
While Spring 4 supports the Java 8 date and time libraries, that does not mean that third party frameworks like Hibernate and Jackson are able to support the Java 8 date and time. At the time of publication Hibernate JIRA contains an open ticket HHH-8844 requesting support for Java 8 date and time APIs in Hibernate. .
Spring 4 and Repeating Annotations
Java 8 added support for repeating annotations and Spring 4 supports these. In particular Spring 4 supports repeating @Scheduled and @PropertySource annotations. For example, notice the repeating @PropertySource annotation in the code snippet below.
@Configuration
@ComponentScan
@EnableAutoConfiguration
@PropertySource("classpath:/example1.properties")
@PropertySource("classpath:/example2.properties")
public class Application {
@Autowired
private Environment env;
@Bean
public JdbcTemplate template(DataSource datasource) {
System.out.println(env.getProperty("test.prop1"));
System.out.println(env.getProperty("test.prop2"));
return new JdbcTemplate(datasource);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Java 8 Optional<> and Spring 4.1
Forgetting to check for null references is a common source of bugs in application code. One way to eliminate NullPointerExceptions is to make sure that methods always return a non null value. For example consider a method like the following:
public interface CustomerRepository extends CrudRepository<Customer, Long> {
/**
* returns the customer for the specified id or
* null if the value is not found
*/
public Customer findCustomerById(String id);
}
A user of the CustomerRepository can write buggy code like this:
Customer customer = customerRepository.findCustomerById(“123”);
customer.getName(); // get a null pointer exception
instead of a correct version as follows:
Customer customer = customerRepository.findCustomerById(“123”);
if(customer != null) {
customer.getName(); // avoid a null pointer exception
}
Ideally we want the compiler to complain if we don’t check that a value can be null. The java.util.Optional class makes it possible to write our interface like this.
public interface CustomerRepository extends CrudRepository<Customer, Long> {
public Optional<Customer> findCustomerById(String id);
}
Therefore the buggy version of the code no longer compiles and the programmer must explicitly check if the Optional contains a value or not by writing code like this.
Optional<Customer> optional = customerRepository.findCustomerById(“123”);
if(optional.isPresent()) {
Customer customer = optional.get();
customer.getName();
}
So the key idea of Optional is to make sure that programmers know that a method can return null, or that they can pass a null value to a method, without having to read the Javadoc. The method signature and compiler will help make it clear that a value is Optional. A more detailed description of the idea of Optional class be found here.
Spring 4.1 supports Java Optional in two ways. The Spring @Autowired annotation contains an attribute ‘required’ so that code such as
@Service
public class MyService {
@Autowired(required=false)
OtherService otherService;
public doSomething() {
if(otherService != null) {
// use other service
}
}
}
can be replaced with
public class MyService {
@Autowired
Optional<OtherService> otherService;
public doSomething() {
otherService.ifPresent( s -> {
// use s to do something
});
}
}
Another place where Optional can be used is with Spring MVC where it can indicate that a handler method parameter is optional. For example:
@RequestMapping(“/accounts/{accountId}”,requestMethod=RequestMethod.POST)
void update(Optional<String> accountId, @RequestBody Account account)
will tell Spring the accountId is optional.
In summary the Java 8 Optional class makes it easier to write code with fewer NullPointerException bugs and Spring works nicely with the Java 8 Optional class.
Parameter name discovery model
Java 8 adds support for preserving method arguments’ names in compiled code. This means that Spring 4 can extract the arguments’ names from methods, and that makes SpringMVC code more compact. For example:
@RequestMapping("/accounts/{id}")
public Account getAccount(@PathVariable("id") String id)
can be written as
@RequestMapping("/accounts/{id}")
public Account getAccount(@PathVariable String id)
Notice that we replaced @PathVariable(“id”) with @PathVariable because Spring 4 can obtain the argument name id from the compiled Java 8 code. The Java 8 complier will write out argument names into the .class files if it is invoked with the –parameters flag. Before Java 8, Spring was able to extract parameter names from the compiled code if the code was compiled with –debug option.
With Java 7 and earlier the –debug option does not preserve parameter names on abstract methods. Therefore for projects like Spring Data that auto generate repository implementations based on Java interfaces this caused a problem. For example consider the interface below.
interface CustomerRepository extends CrudRepository<Customer, Long> {
@Query("select c from Customer c where c.lastname = :lastname")
List<Customer> findByLastname(@Param("lastname") String lastname);
}
Notice the need for @Param(“lastname”) in the signature of findByLastname because even with –debug Java 7 and earlier would not preserve parameter names since findByLastname is an abstract method. With Java 8 –parameters option Spring Data is able to discover the names of parameters on abstract methods so we can rewrite the interface as.
interface CustomerRepository extends CrudRepository<Customer, Long> {
@Query("select c from Customer c where c.lastname = :lastname")
List<Customer> findByLastname(String lastname);
}
Notice that we no longer need @Param(“lastname”) making our code less verbose and easier to read. So when using Java 8 it is a good idea to compile your code with the –parameters flag.
Conclusion
Spring 4 runs on Java 6, 7, and 8 and as a developer you can write your application code using any of Java 6, 7, 8. If you are using Java 8 you will be able to make use of lambda expressions to write cleaner more compact code anywhere there is a functional interface. Since Spring uses a lot of functional interfaces there are a lot opportunities for using lambda expressions with Spring 4.
Java 8 shipped with improved libraries such as the new java.time package and the Optional class which Spring is able to use to make it easier to write clear / simpler code.
Finally, compiling Java 8 code with –parameters option preserves argument names in methods and makes it possible to write more compact Spring MVC method handlers and Spring Data query methods.
If you are ready to start using Java 8 in your projects you will find that Spring 4 is a good framework that makes use of the features of Java 8.
About the Author
Adib Saikali is a Senior Field Engineer at Pivotal with a passion for technology and entrepreneurship from assembly to JavaScript from cold calling to pitching venture capitalists. Adib has been building solutions with Spring and Java for the past 10+ years and is currently focused on helping customers harness the power of big data, PaaS and agile methodologies to build amazing products and services. You can connect with Adib on twitter @asaikali.