Friday, January 31, 2014

The Power of the Arrow

If you haven't yet migrated a project to JDK 8 I recommend reading my post Migrating to JDK 8 with NetBeans first.

After converting projects to JDK 8 I've been opening my eyes to opportunities where I could simplify or better express a block of code by using lambdas and the new collections API's. This post is to share with you some of what I have done and how you might be able to find the same sorts of elegant changes in your code.

Some of these samples may be from other libraries where I have seen how it could be better achieved with JDK 8.

I stated doing these by hand and then slapped myself, NetBeans' hints can do all this for me. With NetBeans generally a hint will appear, you smile and accept it, possibly modify, then another hint might appear so you grin and accept that too; before you know it you've taken 5 or more lines down to 1.

Samples here are all shown in stages of conversion to help with understanding what each step is doing. Sometimes this may mean that the complexity increases which will eventually result in simplifications in subsequent steps.

I've called this The Power of the Arrow in homage to the lambda symbol -> which looks to me like an arrow.

TIP: Many examples here make use of static methods which could reduce code by using a static import for the method, such as Collectors.groupingBy. I have chosen to explicitly include the class name so it is clear where the method is coming from.

Collection Contains Another Collection Entry


There's many different implementations out there, I had my own and only just realised there was one from spring-core that I could share as a source of the example. Here's the method we will be rewriting: org.springframework.util.CollectionUtils.containsAny(Collection<?>,Collection<?>);

Before

This is a fairly common problem, we need to first perform null checks, then perform a for loop testing if the entry is within another collection.

public static boolean containsAny(Collection<?> source,
                                  Collection<?> candidates) {
    if (isEmpty(source) || isEmpty(candidates)) {
        return false;
    }
    for (Object candidate : candidates) {
        if (source.contains(candidate)) {
            return true;
        }
    }
    return false;
}

If you don't want to download the source for spring-core, the following is the `isEmpty' method:

public static boolean isEmpty(Collection<?> collection) {
    return (collection == null || collection.isEmpty());
}

Step 1: Remove the For Loop

NetBeans will be hinting "Can use functional operations".



Accept the hint, you will now end up with the following reduction:

public static boolean containsAny(Collection<?> source,
                                  Collection<?> candidates) {
    if (isEmpty(source) || isEmpty(candidates)) {
        return false;
    }
    if (candidates.stream()
            .anyMatch((candidate) -> (source.contains(candidate)))) {
        return true;
    }
    return false;
}

Step 2: Eliminate one if

Now if you are on the same line, you will now have the "The if statement is redundant" hint.



Accept this hint for the following reduction:

public static boolean containsAny(Collection<?> source,
                                  Collection<?> candidates) {
    if (isEmpty(source) || isEmpty(candidates)) {
        return false;
    }
    return candidates.stream()
                .anyMatch((candidate) -> source.contains(candidate)));
}

Step 3: Eliminate final if

You can now see how simple the method has become, the first if can now be combined with the return statement, this won't show up as a hint but the change is simple.

public static boolean containsAny(Collection<?> source,
                                  Collection<?> candidates) {
    return !isEmpty(source) &&
           !isEmpty(candidates) &&
           candidates.stream()
                   .anyMatch(candidate -> source.contains(candidate));
}

Step 4: Pretty it Up

You can now further refine this as the parentheses are not required for the lambda argument, likewise the lambda body is not required to be parenthesised. I also shorten the lambda argument name.

public static boolean containsAny(Collection<?> source,
                                  Collection<?> candidates) {
    return !isEmpty(source) && !isEmpty(candidates) &&
           candidates.stream().anyMatch(x -> source.contains(x));
}

Summary

Method simplified greatly by removing two if statements and a for loop, we have reduced the vertical footprint from 9 lines to 2, that's a 78% reduction. This could fit on a single line if you're that inclined. What's really cool about this function is the anyMatch can be changed to allMatch or noneMatch, you may want to handle the `isEmpty' differently though.

Extract Key Value From Collection


I often have a need where I want to take a collection of objects, let's say Widgets, of which contains an idproperty that I wish to return a HashSet containing each id that can be used for quick O(1) contains(Object) operations.

Before

Let us take a simplified example where we pass a collection of Widget objects to a function that returns the resulting HashSet<Widget> for each id property.

public Set<String> getKeySet(Collection<Widget> source) {
    Set<String> ids = new HashSet<>();
    for (Widget w : source) {
        ids.add(w.getId());
    }
    return ids;
}

Step 1: Remove for Loop

Within NetBeans you will have see it has identified the for each loop can be converted into a functional stream operation.



Accept this to have the function transformed.

public Set<String> getKeySet(Collection<Widget> source) {
    Set<String> ids = new HashSet<>();
    source.stream().forEach((w) -> {
        ids.add(w.getId());
    });
    return ids;
}

It looks like it's complicated the operation, and yes; it has. But we have a plan to simplify this by not using the forEach and instead using a map and collect

Step 2: Simplify Expression

While it looks busy, we can simplify the expression as the lambda body only contains one line we can remove the braces, further we can remove the parentheses around the lambda parameter too.

public Set<String> getKeySet(Collection<Widget> source) {
    Set<String> ids = new HashSet<>();
    source.stream().forEach(w -> ids.add(w.getId()));
    return ids;
}

Step 3: Map to Keys

Note that in the body of the forEach we are calling the getId method. What we can actually do is use the map function to convert the Widget into a String for the id property before it gets to the forEach

public Set<String> getKeySet(Collection<Widget> source) {
    Set<String> ids = new HashSet<>();
    source.stream().map(Widget::getId).forEach(id -> ids.add(id));
    return ids;
}

That :: thing is a method reference, what it does is say to the function "Hey, here's a lambda expression that I'm going to pass in a Widget object, you give me the result of calling this method reference on that object". This can be really handy as we can omit the need to specify the lambda arguments which makes this easier to read.

Okay, again, it looks busy! But what you might notice now is that we have the stream before it gets to the forEach lambda that it's already in the correct format, a collection of String objects for each id property, woohoo! We can really simplify this now.

Step 4: Reduce Complexity with a Collector

Now the really cool part happens. Since the stream is already in the right format, why not have the stream return the correct type? We do this with a collector by passing a collection type to Collectors.toCollection.

public Set<String> getKeySet(Collection<Widget> source) {
    return source.stream().map(Widget::getId)
            .collect(Collectors.toCollection(HashSet::new));
}

Here, everything is controlled by the stream, we don't even have to construct an object!

Summary

We have performed a small simplification on the original source and a vertical reduction from 7 to 4 (43%) which might not seem like much, but the greater benefit here is that we have moved everything into the stream which can now be read as it's written. Take a look at the final example and read it in the way it's written out loud "source stream map to widget id and collect to hash set", this can be extremely handy when trying to read code later.

Using Method References with JDBCTemplate

While there's many persistence frameworks available I still use spring's JdbcTemplate as our environment has multiple databases within the one application making managing persistence context difficult.

Never mind me though, let me show you something cool you can do with method references.

Before

Here we have a situation where you have to implement a rowMapper instance. There's two ways this is normally done, one; the quick way is with an anonymous class within the call to the query function.

jdbcTemplate.query("select * from customers", new RowMapper<Customer> () {
    @Override
    public Customer mapRow(ResultSet rs, int i) throws SQLException {
        Customer cust = new Customer();
        cust.setId("id");
        // ... set other fields.
        return cust;
    }
});

Usually though we find that you need to reuse the mapper, so you stick it in a dedicated class and create a DEFAULT static instance like this:

public final class CustomerRowMapper implements RowMapper<Customer> {

    public static final CustomerRowMapper DEFAULT
            = new CustomerRowMapper();

    @Override
    public Customer mapRow(ResultSet rs, int i) throws SQLException {
        Customer cust = new Customer();
        cust.setId("id");
        // ... set other fields.
        return cust;
    }

}

And then use it in your query:

jdbcTemplate.query("select * from customers",
                   CustomerRowMapper.DEFAULT);

So the problem is solved. But one thing that would be cool is the ability to group all common domain mappers in one class, and simply have static methods that perform the mapping. Well, now with method references you can do exactly that.

The first thing we do is convert the prior mapper into a simple static class with a static method that handles the mapping, we no longer need to implement the interface, but the method still must adhere to the contract.

public final class SalesRowMapper {

    public static Customer mapCustomerRow(ResultSet rs, int i)
            throws SQLException {
        Customer cust = new Customer();
        cust.setId(rs.getString("id"));
        // ... set other fields.
        return cust;
    }

}

And then use this as follows:

jdbcTemplate.query("select * from customers",
                   SalesRowMapper::mapCustomerRow);

This might not look like a huge benefit over the dedicated RowMapper implementation, however you can now put all your domain mappers in one place, or have row mappers calling each other for base objects sharing similar structures.

As Dan Ciborowski has pointed out a builder pattern would also make this even more expressive making the method for mapping customers very clean, hypothetically the mapCustomer method might look something like this:

public static Customer mapCustomerRow(ResultSet rs, int i)
        throws SQLException {
    return Customer.builder()
            .id(rs.getString("id"))
            .name(rs.getString("name"))
            .build();
}

Group Objects Into Map

Often you have a need to group objects by a particular property of that object into a map that contains a list for the map values per entry. This is usually a tedious task that can often contain errors, i.e. forgetting to add a list to the map for a given key if it does not exist and then trying to add to it.

Before

One such implementation that groups customers by state might look like the following:

public static Map<String, List<Customer>> 
        groupByState(Collection<Customer> custs) {
    Map<String, List<Customer>> res = new HashMap<>();
    for (Customer cust : custs) {
        if (!res.containsKey(cust.getState())) {
            res.put(cust.getState(), new ArrayList<Customer>());
        }
        res.get(cust.getState()).add(cust);
    }
    return res;
}

TIP: with JDK 8 the compiler is now smart enough that the explicit type argument on line 5 is no longer required. JDK 7 required that this be explicitly defined. NetBeans will give you the "Use functional operation" hint, if you accept it it will look something like the following:

public static Map<String, List<Customer>> 
        groupByState(Collection<Customer> custs) {
    Map<String, List<Customer>> res = new HashMap<>();
    custs.stream().map((cust) -> {
        if (!res.containsKey(cust.getState())) {
            res.put(cust.getState(), new ArrayList<>());
        }
        return cust;
    }).forEach((cust) -> {
        res.get(cust.getState()).add(cust);
    });
    return res;
}

This really isn't what we want. To get to where we want to go we really need to think about the collect method.

Use a Collector to Group By

I'm not going to attempt to show how to refactor this as it's easier to just start from scratch, it's barely any code :)

As earlier we can take a look at the Collectors methods to see what we could use. We find the method Collectors.groupBy. Making use of this method really is simple.

public static Map<String, List<Customer>> 
        groupByState(Collection<Customer> custs) {
    return custs.stream()
            .collect(Collectors.groupingBy(Customer::getState));
}

You can simplify further by statically importing Collectors.groupingBy

public static Map<String, List<Customer>> 
        groupByState(Collection<Customer> custs) {
    return custs.stream().collect(groupingBy(Customer::getState));
}

Summary

We have taken a complex 11 line block ant transformed it into a 3 line block giving a 75% reduction which is super simple to read from left to right.

Nested Grouping

Building on the prior example, say we wanted to further group our customers into state and city.

Before

As we are building on the prior example we use it to represent an even more complicated method that must create double Map structures.

public static Map<String, Map<String, List<Customer>>>
        groupByStateCity(Collection<Customer> customers) {
    Map<String, Map<String, List<Customer>>> res = new HashMap<>();
    for (Customer cust : customers) {
        if (!res.containsKey(cust.getState())) {
            res.put(cust.getState(),
                    new HashMap<String, List<Customer>>());
        }
        if (!res.get(cust.getState()).containsKey(cust.getCity())) {
            res.get(cust.getState()).put(cust.getCity(),
                                         new ArrayList<Customer>());
        }
        res.get(cust.getState()).get(cust.getCity()).add(cust);
    }
    return res;
}

After

The above is horrible and hard to maintain, lots of things can go wrong. We can greatly simplify this by passing another groupBy collector to the collector.

Again, I'm not going to attempt to show how to refactor this.

public static Map<String, Map<String, List<Customer>>>
        groupByStateCity(Collection<Customer> customers) {
    return customers.stream()
            .collect(Collectors.groupingBy(Customer::getState,
                             Collectors.groupingBy(Customer::getCity)));
}

This is now super compact, it can again be further simplified if you statically import Collectors.groupingBy.

public static Map<String, Map<String, List<Customer>>>
        groupByStateCity(Collection<Customer> customers) {
    return customers.stream()
            .collect(groupingBy(Customer::getState,
                                groupingBy(Customer::getCity)));
}

Conclusion


I've given just 5 examples you can reuse in your own code that I have found crop up. There are many examples all over about lambdas in collections, I highly recommend reading Brian Goetz's article on The State of the Lambda: Libraries Edition.

There is a bit of interest in LINQ from Java developers that I'm thinking of writing java 8 versions of the 101-LINQ Samples, some may not be possible such as those that make use of anonymous types though I think the exercise would be useful.

Wednesday, January 29, 2014

Migrating to JDK 8 with NetBeans

One of the most widely anticipated features for JDK 8 has been the inclusion of lambda expressions into the language. If you're like me, you've been eagerly following this since before JDK 7 was released. I got my hunger satisfied by using lambda expressions in C# on projects since early betas of .NET 3.5 in 2007 and would eagerly follow Brian Goetz's articles on the state of the lambda and anything else I could get my hands on, I really have wanted lambdas in Java for so long.

Now I'm so excited to start using them! I can see JDK 8 being released and have started migrating projects on a jdk8 branch which will be ready to be snapped into production as soon as the JDK is officially released, I strongly advise everyone to do the same, you will love the new support.

What I'll be Covering

There are several new features coming with JDK 8, of which I'm going to cover a small subset of what I believe will be the most widely used by consumer developers, see the full JDK 8 feature list for more.
  • JSR-335 - Lambda Expressions and associated library improvements such as bulk data operations for collections.
  • JSR-310 - Date and Time API.
  • NetBeans tooling to help migrate to JDK 8
  • Maven based project

Environment

For the conversion we'll be using NetBeans, quite simply because IMHO it is the best IDE for Java development, if you haven't tried it in a while you're in for a heck of a treat (sorry eclipse). I've been using it since 2002 of which the team has always responded quickly to change and bug submissions. The upcoming 8.0 version has full support for JDK 8 and an awesome tool that will analyse your project and convert (with your permission) blocks of code to JDK 8 equivalents all in one bulk migration operation, this is what we're going to use. Download the Beta 8 version or the nightly build.

NetBeans comes with all you will need to get the job done, but you will also need a copy of the latest JDK 8 EA which can be downloaded here.

I converted two distinct projects, one was a super project containing 20 child projects that represent domains within our business such as sales, product, scheduling... The second project is a spring/mvc 3 project for our in house FSA/CRM/portal system used by all members of the company that relies on the 18 libraries in the first project.

I use maven/git from the command line a lot, but that's a personal preference. NetBeans has great support for both, but I just type faster than I can click.

At the time I performed the conversion I was using the nightly build of NetBeans (Build 201401260001) and JDK8 (build 1.8.0-ea-b124).

Configure NetBeans IDE

While NetBeans does work well with JDK 8, I have had some rendering issues, it could be due to being on a mac, I don't know. But for me I always point it to JDK 7. I recommend doing this until the final release of both JDK 8 and NB 8. To do this you will need to locate the etc/netbeans.conf file within your NetBeans installation. On a mac this is slightly different. Refer to the following default locations where your netbeans.conf may reside:
  • Windows: c:\program files\netbeans\etc\netbeans.conf
  • Mac: /Applications/NetBeans/NetBeans\ 8.0\ Beta.app/Contents/Resources/NetBeans/etc/netbeans.conf
  • Linux: /opt/netbeans/NetBeans\ 8.0\ Beta/etc/netbeans.conf
At around line 57 there's a line for setting the JDK, on my platform it needed to be set as follows for JDK 7.
netbeans_jdkhome="/Library/Java/JavaVirtualMachines/jdk1.7.0_51.jdk/Contents/Home"
NOTE: If you have previously used a dev build of NetBeans, it's best to delete your dev userdir, this can be found in ~/netbeans/dev on linux, ~/Library/Application\ Support/NetBeans/dev/ for mac, I'm not sure where in windows.

Now start NetBeans, we will need to then add the Java 8 JDK to NetBeans. Go to [Tools > Java Platforms], from here add your installed Java 8 JDK.



Change JDK Version for Servlet Container(s)

One thing I actually forgot to do when first writing this post was to update the JDK on my tomcat container. I found this out after exceptions started appearing when debugging locally. All servlet containers are configured similarly, thought he JDK setting is in slightly different places. All servers can by configured by either right clicking the server in the Services (CMD+5 on Mac) within the servers node or by accessing the Tools > Servers menu option.

I have only covered Glassfish and Tomcat as they are the standard containers that come with NetBeans, if you have another container try poking about until you find the setting.

Tomcat

Tomcat's configuration is found under the "Platform" tab. The "Java Platform" configuration option controls which JDK is used.



Glassfish

Glassfishes configuration is found under the "Java" tab. The "Java Platform" configuration option controls which JDK is used.

Upgrade Project JDK

First thing to do is for each project update the JDK and source version. This can be done by right clicking the project and clicking "Properties".

You will need to first set the JDK under "Build > Compile", set the "Java Platform" to "JDK 1.8".



Next go to "Sources" and set the "Source/Binary Format" to 1.8.



NetBeans would do the following:
  • Add or update the maven-compiler-plugin version to 1.8
  • Create or modify the nb-configuration.xml file setting netbeans.hint.jdkPlatform to JDK_1.8
You may wish to alter your pom.xml file directly, for me I actually have a property version-java-target in my pom.xml properties section that defines the 1.8 java version. The use of the nb-configuration.xml file gives the IDE a hint as to which JDK it should use, maven from the command line will use the setting of JAVA_HOME, but NetBeans may have more than one JDK configured so has to somehow figure out which one to use.

NetBeans first looks at nb-configuration.xml for the JDK hint (netbeans.hint.jdkPlatform), if it can't find it there it will look at the pom.xml file for the property of the same name. If you do want to control this yourself you may edit the pom.xml directly but remember to reload the project (right click > Reload POM) to reload the new configuration.

TIP: If later you find that you are creating JDK 8 specific code but the IDE tells you your source level does not permit this, yet checking the project configuration tells you it's all set for JDK 8 and source level 1.8, chances are you have a nb-configuration.xml file and pom.xml conflict with netbeans.hint.jdkPlatform, in this case the nb-configuration.xml file wins. This caught me out where a child project was involved. Basically NetBeans was telling me the configuration from the parent pom.xml file, yet the child projects had a nb-configuration.xml file that was conflicting.

At this point you will need to update the project JDK on your CI/build server before pushing your changes if you wish to avoid build failures.

Repeat this for your other projects you will be upgrading.

TIP: If you are working with a super pom and child projects, I suggest the following:

  1. Remove all nb-configuration.xml files (look at them first), i.e. find . -type f -name nb-configuration.xml -delete.
  2. If any child project contains a maven-compiler-plugin build plugin, remove it.
  3. In your parent pom add the plugin management with default settings which will get picked up by child projects, refer below.
  4. Set the JDK version in your properties section.
<properties>
  <version-java-target>1.8</version-java-target>
  <netbeans.hint.jdkPlatform>JDK_1.8</netbeans.hint.jdkPlatform>
</properties>
<build>
  <pluginmanagement>
    <plugins>
      <plugin>
        <groupid>org.apache.maven.plugins</groupId>
        <artifactid>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>${version-java-target}</source>
          <target>${version-java-target}</target>
          <encoding>${project.build.sourceEncoding}</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupid>org.codehaus.mojo</groupId>
        <artifactid>jaxb2-maven-plugin</artifactId>
        <version>1.5</version>
      </plugin>
    </plugins>
  </pluginManagement>
</build>


Build your project within the IDE (SHIFT+F11), if all goes well, send it off to your DEV CI server. You should be right to use new language features.

Test Project 1.8 Support

Now that you've configured all projects I advise testing that the project is in fact being picked up by the IDE as a JDK 1.8 project, as noted above I had issues where this was not. It's simple to test, just open a class and add the following:

Runnable r = () -> System.out.println("I love lambdas");

If your editor shows an error like the following, the project isn't picking up the Java 8 JDK sometimes it's necessary to close the IDE and open again to get it to recognise the new JDK settings for the project. If it isn't showing an error, then it's time to get excited!



Configure Formatting Preferences

NetBeans has a highly customisable configuration for the way code is formatted, which can be specified globally and per project. To configure settings globally Go to NetBeans Preferences (Mac: NetBeans > Preferences, Windows/Linux: Tools > Options) > Editor > Formatting > Java. Browse through all the available settings and get these the way you like them. I advise doing this now as later we will be converting a lot of source files automatically at once which will use these settings.



Inspect and Transform to JDK 8

This is by far the most super-coolTM feature that will hunt down potential JDK 8 conversions that can be applied to your source files. It will present a list of all found changes with diff's allowing you to accept each individual change. This is truly cool.

TIP: Note that this can do more than just convert to JDK 8, have a look at the configurations and see what else you might like it to find/fix for you.

Start by selecting a project node, you can right click and select "Inspect and Transform..." or go to [Refactor > Inspect and Transform...]. Select the "Configuration" radio button and change the selection to "Migrate to JDK 8".



At this point you can customise how the migration assistant works by hitting the "Manage..." button, I highly recommend you do this; my tip is to turn OFF the "Static Imports" setting. There's a good reason for this which is illustrated here.



Consider this fairly useless piece of code, though it's functional that it shows how something can go awry.

import java.util.Calendar;
import java.text.DateFormat;
import java.util.GregorianCalendar;
import java.text.SimpleDateFormat;

public class StaticUsage {

    public void foo() {
        Calendar cal = GregorianCalendar.getInstance();
        DateFormat df = SimpleDateFormat.getInstance();
    }

}

With static imports on the migration tool will produce the following invalid class file.

import java.util.Calendar;
import java.text.DateFormat;
import java.util.GregorianCalendar;
import java.text.SimpleDateFormat;

import static java.text.DateFormat.getInstance;
import static java.util.Calendar.getInstance;

public class StaticUsage {

    public void foo() {
        Calendar cal = getInstance();
        DateFormat df = getInstance();
    }

}

Notice here that the code produced now has a conflict between the two static methods.

Back to our migration assistant, hit Inspect. The inspector may take some time depending on the size of your project. Once complete you may have a warning where more than one fix can be applied, if this is the case take note of where and just hit "Inspect".



You should now have a diff open up within the output window area titled "Refactoring". From here you can review all refactorings that have been identified by the IDE. Give each one a review and un-tick if you do not want that particular action to be taken. Once satisfied hit the "Do Refactoring" button.

All your selected refactorings will have now been applied. First before continuing just ensure the project compiles by performing another clean and build (SHIFT+F11).

Clean up Source Files

While the JDK 8 migration tool is completely awesome, you may want to review all files and give them a polish. There's a few things that I found that I wanted to do when reviewing my sources:
  1. Lambdas now removed a lot of anonymous classes, therefore their import statements can now also be removed. To fix this, open each file and simply hit [Source > Fix Imports...].
  2. Lambdas in most cases can type infer the arguments. I personally prefer this as IMHO less is more when it comes to readability. Simply find any lambda arguments and remove the type. Note that sometimes this can't be done as the compiler may not know which method is being called for example.
  3. Lambdas do not require a body, which NetBeans prefers. However I found that in some (minor) cases it's actually more readable when there is a body.
  4. The refactoring sometimes ends up on very long lines, especially where the lambda has no body. I found it far easier to read by going through every lambda and breaking them out a little.

Check out Some Hints

NetBeans has more to offer with migrating to JDK 8. For example it can convert a for loop to a map/reduce stream. Sometimes this may not be ideal though, consider the following:

Set<String> ids = new HashSet<>();
for (Widget w : getAllWidgets()) {
    ids.add(w.getId());
}

If we were to accept the hints suggestion and convert, it would be more of a horizontal problem:

Set<String> ids = new HashSet<>();
getAllWidgets().stream().forEach((w) -> {
    ids.add(w.getId());
});

Which if fully converted should actually look like the following:

HashSet<String> ids = getAllWidgets().stream()
        .map(Widget::getId)
        .collect(Collectors.toCollection(HashSet::new));

To stop this hint appearing all the time, configure it to only appear on current line.

Java Time

Java now comes with a great date/time library out of the box, so you don't need to worry about JodaTime, which served a need for many years. Fortunately Java Time (JSR-310) uses similar class names to that of JodaTime, which will make your life slightly easier in the fact that some source files will just need the references changed from org.joda.time.* to java.time.*. Choosing to upgrade to java-time or continue using Joda or java.util.Date/java.util.Calendar is up to you, but I advise doing it sooner than later. Updating is a matter of going through and performing manual conversion unfortunately.

What I will describe is what you might need to do to get jsr-310 working with Jackson and spring.

As with joda-time, it comes as a separate jackson module. Add the following to your pom.xml

<dependency>
  <groupid>com.fasterxml.jackson.datatype</groupId>
  <artifactid>jackson-datatype-jsr310</artifactId>
  <version>2.3.0-rc1</version>
</dependency>

As for spring there are a number of ways this is done, if you are using joda already, use that same class/configuration, but also add com.fasterxml.jackson.datatype.jsr310.JSR310Module with the registerModule method just as you would with joda. For example, this is the configuration I use:

Custom object mapper class

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
import javax.annotation.PostConstruct;

public final class CustomObjectMapper extends ObjectMapper {

    public CustomObjectMapper() {
        super();
        registerModule(new JodaModule());
        registerModule(new JSR310Module());
    }

    @PostConstruct
    public void afterPropertiesSet() {
        disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    }

}

Spring MVC servlet context configuration

<annotation-driven>
  <message-converters register-defaults="true">
    <beans:bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
      <beans:property name="objectMapper">
        <beans:bean class="com.johnsands.spring.http.converter.json.CustomObjectMapper"/>
      </beans:property>
    </beans:bean>
  </message-converters>
</annotation-driven>

Conclusion

Here I've shown you how you can quickly migrate your project to JDK 8 fairly painlessly with the aid of the awesome tools that come with the NetBeans IDE that help to perform this migration. I really tip my hat off to the team as this was a nice surprise that I thought I was going to have to convert everything on a per case.

Have a look at my second article The Power of the Arrow for some of my more interesting lambdas.