Java 9 is an unusually-complex Java release. It comes with deep changes to some long-held norms, compatibility-breaking changes at build time and run time, and a new release cadence. There’s a lot of great stuff, but development teams face tough decisions about what to migrate, how to migrate it, and when to do so.
Here at PreEmptive, we have an especially-complex problem because our flagship Java application, DashO, runs on the Java platform, integrates deeply with the Java platform, and supports apps developed with nearly all versions and implementations of the Java platform. DashO’s migration to Java 9 requires deep understanding and extensive care to ensure that DashO continues to be able to inspect, obfuscate, and inject code into apps across all those platforms, while preserving behavior, performance, stability, and portability.
So we’ve been hard at work on our own migration plans, and we want to share what we’ve learned. Hopefully this article will make your Java 9 migration planning a little easier.
We see three big factors that define the shape of any Java 9 migration plan:
Let’s take those one by one.
Modularization is the headline feature of Java 9, and it almost didn’t make it into the release. Now that it’s here, it is paramount for Java developers to understand what it is (and what it isn’t!), and how it will affect their apps.
We see two major ways modularization might affect an app.
First, modules change the way classes are located and how access to them is restricted. It’s no longer enough to understand
private and the classpath; now we have to worry about the modulepath and whether packages (as a whole) are visible to a given class. Apps that want to take advantage of module features – or that depend on libraries that are modularized – have to follow the new access rules, both at runtime and at build time. Apps that want to override behavior in other modules have to jump through new hoops to do so.
From a planning point of view, this means that app developers have to carefully consider not only their own modularization, but also the modularization of any libraries they depend on.
Second, the JDK and JRE have themselves been modularized. For app developers, that means a few important things:
.jar files are (mostly) gone, replaced by
.jmodfiles in the JDK and by an opaque binary in the JRE. So tools that integrate tightly with the JDK have to be updated to handle the new distribution layout.
Beyond those two major changes, there are other minor consequences of modularization. For example, stack traces will now show module names.
Putting it all together, modularization is the first big issue for most Java 9 migrations, and each part of the issue requires specific understanding and planning.
Oracle put a lot of effort into making Java 9 mostly compatible with Java 8. For many developers, migrating to Java 9 will be straightforward, and Oracle deserves a lot of credit for making that possible, even as modularization made wide-reaching changes to the entire Java platform. However, most complex apps will have a complex migration path, both because of modularization and/or because they have to support multiple Java runtime versions (i.e. 8 and 9) at the same time.
To help with these more-complex scenarios, Oracle made it possible for developers to partially migrate their apps, in two ways.
First, in terms of modularization, they’ve made it possible to ship apps with a mix of modules and non-modules, through the use of automatic modules (a way to treat a jar file as a module, even if it wasn’t built that way) and the unnamed module (a way to fit non-module code into the module paradigm). These tools make it possible for developers to ship partially-modularized apps – but they have to learn and understand these concepts, and how they come into play at build time and at runtime, to do so.
Second, and independent of modularization, Oracle added a multi-release jar feature that makes it possible to write Java-9-specific code (that uses Java 9 features when run on a Java 9 JRE) while still shipping a Java-8-compatible app. This is great for library vendors that want to adopt new features, but it will be difficult to get the build process right, and testing will be a major concern.
Both solutions – mixed modularization and multi-release jars – provide a way for developers to incrementally migrate to Java 9. But both solutions also make development more expensive. Developers have to understand more, the build process gets more complex, the testing process gets more complex, and the production code gets more complex. But for the biggest apps, these solutions will be welcome.
For someone planning a migration to Java 9, if they don’t want to take an “all or nothing” approach, understanding these two solutions is key.
Concurrently with Java 9, Oracle also introduced a new release cadence, a new version-numbering strategy, and releases that won’t get long-term support. These three changes go hand-in-hand.
The new cadence is to release every six months, with whatever new features are ready in time. Past releases will only get fixes, not features. Each new version will be numbered according to its release date (although this is contentious), so a release in March of 2018 will be versioned as 18.3. Then every three years a release will be declared as a “long term support” (LTS) release, and will be maintained for at least five years. (Non-LTS releases will only be maintained until the next non-LTS release.)
It isn’t clear (yet) which releases will have breaking changes, and whether the version-numbering will indicate that somehow.
All of this actually starts with Java 9, but in some surprising ways. First, Java 9 isn’t versioned as “17.9” (nor as “1.9” as per historical precedent) so it will always have a version number that doesn’t fit with either paradigm. Second, Java 9 is not an LTS release, so as soon as 18.3 is out, Oracle will stop supporting Java 9 altogether. In fact, the first LTS release will be 18.9 (September 2018) – so the next release (18.3) will also not have long-term support.
This leaves developers in an unusual situation: Java 9 is a major release with breaking changes, but if they go through the effort of migrating to it before March, they’ll have to immediately migrate to the next version (18.3) and then again to 18.9, at which point they could choose to stick with that version for a few years. For developers (and enterprises) that want a slower Java release cadence, it doesn’t make much sense to migrate until 18.9. For developers who would prefer to update often, migrating to Java 9 now might make sense.
If you’re thinking about a Java 9 migration, you’ve got three major topics to consider:
If you work through those questions, you’ll find that most aspects of your plan emerge naturally from the answers.
You might have noticed that this article doesn’t include details about our plans for DashO. If you’re a client, then you probably already know that PreEmptive doesn’t “sell futures” by publishing speculative roadmaps and promising features. If you’re not yet a client, you might be interested to see that we have a long history of supporting Java and Android releases in a timely manner 🙂
Regardless, you can also always contact us directly; we love to talk with our customers, both to share our plans and to hear more about yours, and we’d be happy to work with you to manage any short-term needs you might have.
Also please rest assured, even as we’re hard at work on Java 9, we’ll continue tracking the latest Android versions, keep fighting back against new decompilers and de-obfuscators, and keep providing enterprise-class support.
Do you have your own Java 9 migration insights? We’d love to hear them!