What Migrating to Java 8 Does to Your Codebase
This article, originally written in 2014, gives some lasting insights into how migrations from Java 6 or Java 7 to Java 8 can affect your codebase. The examples presented in this article do a nice job of articulating potential problems with the introduction of lambdas, streams and first-class methods.
What to Look for When Java 8 Hits Your Code
Wouldn't you know it, Java 8 is just around the corner! This long-awaited release has been scheduled for March 18th, which is just about a month away now. In fact, you can already download and try release candidate build. But with this new release, many developers will find themselves migrating to Java 8.
This release is very important for various reasons. I like to think that the introduction of defender methods is the most life-changing out of all the incredibly interesting improvements in this release. It enables an easier evolution of the standard library, which, in turn, promises faster improvements in upcoming releases.
Of course, you’re already aware of lambda support that comes in to bring Java closer to all those cooler JVM-based languages that have had functions as first-class citizens for ages. With lambdas, Java gets bulk operations (aka Stream API) and easier computation parallelization. Other changes include a reworked Date & Time API, improved cryptographic primitives, Nashorn JS engine, the improved Fork-Join framework and a bunch of changes for concurrency support.
Anyhow, the list of what exactly has changed goes on and on, but not all changes are equally important for application code. Some of them, like getting rid of permanent generation space doesn’t influence lots of applications directly. In this post, I’ll take a small project that’s compatible with some older Java language specification and try to migrate it to Java 8. I intend for this migration to be done by hand, without any of the magical transformation features that your IDE can offer. The goal of this exercise is to see how a migration can go, which parts offer the most challenges and what goes fluently.
Sample Project of my Choice
For this experiment I chose my favourite zip processing library: zt-zip. Main reasons to pick zt-zip are the following:
- It's a free-available, open-source library, so if I get some Java 8 idioms wrong, someone smarter will correct me.
- It's currently Java 4 (yep!) compatible, so there are millions of changes to introduce, which is both good and bad. The new version can be dramatically different, but at the same time, the significance of those diffs can lack immediate applicability for migration from newer versions of Java.
- As a library to process zip archives, it includes lots of iteration over archive entries, so streams will be handy, and callbacks can be specified as lambdas. It also includes some Reflection API use, so I can see if having method parameter names at runtime make using reflection nicer.
- Also, the repository includes some unit tests, so my refactoring can be more-or-less validated.
Disclaimer: I’m actually one of the maintainers of this library and I’m familiar with the code so this experiment might actually be useful. :-D
Cloning Your Maven Project for Migration
First of all, let’s get a fresh clone of zt-zip:
git clone [email protected]:zeroturnaround/zt-zip.git
Since this is a Maven project, you can set the source and target version for the compiler plugin to 1.8.
The first thing we notice when running tests after this change is that runtime warns us about lack of permanent generation memory.
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=384m; support was removed in 8.0
I haven’t found how to make Maven leave PermGen options unspecified, but since all the tests pass despite this warning, I chose to ignore it. If you happen to know what should I’ve done with it, please tell let me know in the comments section below or via Twitter at @shelajev.
Updating Code With Lambda Expressions for Migration
Moving on, I find this perfect place to replace some ugliness with a lambda.
Here we just want to say if a given file is a zip archive and whether there are any entries with specified names in it.
Iterating over an array with Stream API and writing lambdas in an expression form doesn’t feel like writing Java code at all. Of course, this is the first thing that caught my eye and though it's the simplest example, it's important to see this in real code as opposed to only in tutorials and workshops.
Filtering Streams With the Filter Method in Java
Not everything is that easy with streams in Java. Here we iterated over a collection of names again to check if the corresponding entries are directories. The name of the method screams that it can use some love from streams, namely filtering a stream with the filter method.
An idiom with filter and collect is something we will be seeing quite often when working with Java 8. Personally, I don’t like collect as it is because it feels extremely verbose, especially the accumulating part.
Another thing that annoys me here is that streams don’t know anything about exceptions (i.e. checked exceptions). I suppose throwing an exception from a lambda argument would terminate iteration and bubble up where I expect it to. However, as the function’s signature doesn’t feature any throws clause, I have to wrap my lambda in a try-catch block, and then wrap the whole iteration again, just to cover the cases where I can't open my file in general.
Also if you were breaking a loop with “break”, then it will be hard to come up with an analog within the streams universe. An exception will do, but the code becomes ugly pretty quickly when you throw random
Also, you should be a bit suspicious of
Stream.parallel() method calls; it is so easy to introduce, it might seem like a good idea, but it won’t necessarily improve performance and it can create concurrency bugs that could possibly slip through your test suite.
Our zt-zip doesn’t do much with date or time, so I couldn’t find a place to try that new API, and the time I had set aside for experimenting came to an end anyway, so I won’t be showing more diffs here.
Java 8 is a much better language from the developer perspective than any version of Java before. It won’t remove all those complaints about verbosity and not having all the features that coding humanity has come up with so far. However, the introduction of lambda expressions and first-class methods makes programming much more enjoyable.
Lots of small functions, callbacks, error reporting facilities are more readable now. The Stream API is easy to grasp and pleasant to use. Although it is not a drop-in replacement for all your for-loops, and sometimes the code looks slightly messier than before, it is a good addition to the library.
There are two basic things without which you will regret migrating your project: 1. good unit test coverage and 2. IDE support. And since I can't do anything about the tests in your project, at least I can share some links about IDE support for Java 8 features.
So here's that:
- IntelliJ Idea has support for early availability Java 8 builds out of the box. Refactorings like converting anonymous classes to lambda functions, going back and forth between expression and statement lambdas, warnings on the side of editor. It’s been pleasure to have this migrating experiment with proper support.
- Eclipse will release its official Java 8 supporting builds only after the prior is out in the wild, however currently you can enjoy its beta implementation.
- NetBeans also has support for Java 8 features enabled since the release of 7.4.
If you haven’t played with Java 8 before, look into it; infrastructure and tools are already there for you. Don’t forget that moving forward is not only healthy, but essential to performance and security.
Need more help with Java 8? We've got a handy cheat sheet to guide you.
Hey, thanks for checking out my article! Be sure to tweet me @shelajev.
Looking for further reading on Java 8 pitfalls? Read our article on how to avoid ruining things with lambdas!