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.

1 comment:

  1. I've added a section for configuring the JDK within your app-servers. Here I cover Tomcat and Glassfish

    ReplyDelete