We’ve previously covered at What to Look for in Java 8 Code, now Java is moving faster than ever it’s time to do an update and cover what to look for in Java 9 code. While Java 9 has even now been replaced with Java 10, and Java 11 in coming in September, these Java 9 features are, of course, available in Java 10 and 11. If your application is using any version later than Java 8 you may benefit from these tips.
We’ll be looking at the code in IntelliJ IDEA using the Upsource plugin for these examples so we can leverage the full power of the IDE for reviewing Java 9 code.
What we’re not going to cover
Firstly let’s talk about the Java 9 features we’re not going to mention in a code review context:
- Although we’ll look at code that uses the Java Module System, we won’t talk about times when you might introduce the module system to an existing application. Migrating to use Java modules is likely to be a non-trivial architectural decision, and is probably not something that would be suggested during a standard code review.
- The REPL, JShell, is a fantastic new tool for experimenting with code, and can be very useful for seeing how new features work. However, using it is likely to be part of the process of coming up with a solution, it’s less likely affect the artifacts we look at during a code review (although it is possible to create and run scripts with JShell), so we won’t cover JShell either.
If you are making use of the new Java Module System (sometimes referred to as Jigsaw), there are things a reviewer can look for in order to simplify code or make things easier to understand. If you’re using IntelliJ IDEA, I highly recommend getting the IDE to generate the module diagram from a module-info.java file so you can visualize the modules.
Consider ordering the entries in module-info.java
This may not matter to you and your team, but I like my code files to have things in a predictable order. For module-info.java files, I like to have my requires at the top of the file and the exports grouped together after this.
If you look at the Java platform code (like the example above), you can see they’ve grouped the declarations by type, and they are in a consistent order. This way it’s easy to see at a glance the parts that you’re interested in, whether it’s the dependencies or the code that is exposed (exported) by this module.
Make sure the code is “opened” only where necessary
One of the goals of modularity was to improve the security of our code. It does this by no longer allowing reflection to automatically work on all code. If we use libraries that rely on reflection to work (and many of them do), we may need to explicitly declare this in our module-info.java file.
Although the easiest solution is to simply declare the package open (or, even worse, put the
open keyword in front of
module right at the top, opening all the code in the module), your code will be much safer if you specify the modules that are allowed access to the package
Transitive dependencies don’t need to be in module-info.java
Generating a diagram of the module dependencies can help a reviewer see what’s being used.
In the diagram below, the blue lines represent transitive dependencies – that is, any module that depends on the first module automatically gets a dependency upon the module at the arrow-end of the blue line.
In this above diagram, my code (
com.mechanitis.demo.sense.client) has a dependency on
javafx.base, represented by a thin black line. It also depends on
javafx.fxml, which both have a requires transitive
javafx.base in their module-info.java files, and is represented on this diagram with the blue line. Therefore, I could, if I wanted to, remove my explicit dependency upon
javafx.base and everything will still work.
This is a matter of taste – you might want to be explicit about everything you require, or you might want the module-info.java file to be as minimal as possible.
Check the level of visibility of classes and the exports in module-info
Now we have true modules in Java, we have the visibility level that was always missing – code that needs to be used by other packages but isn’t really “public”. Now, if your code has a module-info.java, your public classes aren’t necessarily public, i.e. they can’t by default be accessed by everyone. This is good for security, and also for being able to provide stable APIs but be able to change the internals without affecting users.
In the example above, the code author has increased the visibility of a class from package-level to public, and moved it into an
internal package. Since this is in a Java 9 module, this means that the
StringMapperProcessor class is now visible not only to classes in its package, but to all classes in the module. This is perfectly OK if this utility is to be used by classes from different packages in the same module.
One thing you should be checking as a reviewer is that the author hasn’t exposed classes that are meant to be internal to other modules.
In this example, the whole
internal package has been made public outside of the module. Since this is meant to contain code that is only to be used by the module itself, this is negating some of the benefits of the module system.
In this case, the reviewer might suggest:
- Moving a specific class that needs to be accessed from outside the module into a package that is already exported.
- Creating a new, more suitable, package to export with the classes that need to be public outside of the module in it. Note that you can also export to specified modules, so if the functionality should only be visible to known modules you can specify this as an
exports... todirective (see this cheatsheet for useful information about directives in module-info.java).
- Duplicating the required functionality in the module that needs. Small amounts of duplication can be a much better solution than compromising encapsulation.
In all cases, the offending
export statement needs to be removed.
Note that in this example, the package is very specifically called “internal”. This can, of course, contain sub-packages too. This naming convention makes it very clear that encapsulation has been violated when a package containing the word “internal” has been added to module-info. This makes a reviewer’s job easier as they can see this problem straight away without having to know which packages should be kept “secret” inside the module and which are OK to expose to other modules. You might want to consider a similar naming convention for your own modules.
New Collections Factory Methods
Moving on from Java Modules, let’s look at some other examples of code that can benefit from new features in Java 9.
Java 9 added the Convenience Factory Methods for Collections. This means it’s now easier to create immutable collections with a fixed set of contents.
IntelliJ IDEA (and therefore the code intelligence available inside Upsource) can point out many examples where these can be used, so a code reviewer will be able to spot some places that can benefit from the new factory methods immediately.
As a reviewer you probably want to make sure any newly added or edited code uses the new methods, but you may also want to check any files that are changed in this review for existing examples that can be migrated to the new style.
Not all examples will be obvious. Lots of code uses the
Arrays.asList method to create a list from a series of values and then uses that List as if it were immutable (
Arrays.asList creates a list that cannot be appended to, but the individual values can be changed so it’s not actually immutable). When reviewing code that uses
Arrays.asList you may want to suggest using
List.of, if possible, not because it saves 6 characters, but because it creates an immutable List which is safer to pass around without unexpected side effects.
If the code is doing anything that looks like it’s manipulating the Operating System, this may be simpler with Java 9. The existing process API has had a lot of improvements, too many to list here, so advise for code reviewers is: if the code is using the Process API, or doing things like trying to obtain the PID for a process, research the updated Process API and see if there’s an easier way to do it.
Prior to Java 9, looking for information in the stacktrace was expensive, as the whole stack needed to be constructed and then we might do something to look at the information there.
Java 9 adds a StackWalkingAPI which can potentially reduce the cost of looking for information in the stack trace.
Using this API is a little involved, and note that if the operation you want to do processes every element in the stacktrace (like the code above does), you may not get any performance improvements. As always, if performance is important it should be tested.
Private methods on interfaces
Java 8 added the ability to put behaviour on interfaces via default and static methods, which was particularly useful in the context of lambda expressions, and for adding features to existing APIs without breaking backwards compatibility.
However it did potentially lead to some code duplication.
Since by definition all methods on an interface are public, creating a method containing common functionality meant adding a new public method to the interface, which may not have been desirable.
In Java 9, interfaces can have private methods, so code reviewers can suggest extracting common code into a private method.
Java 9 has added parameters to the @Deprecated tag so we can make it clearer what the intent behind the deprecation is. The first is a String value,
since, which can contain the version when this code was deprecated, the second is a boolean value,
forRemoval, which is set to false by default but should be true if this method is going to be deleted or hidden in the future.
When reviewing code that has a
@Deprecated annotation, a reviewer can suggest that extra information is added to this if appropriate.
If the intention behind the deprecation is to remove this method, the
forRemoval flag should be set to
The flip side of this is when reviewing code that calls deprecated methods, it becomes clear how urgent (or not) it is to stop using a deprecated method. If using a deprecated method that’s marked to be removed, IntelliJ IDEA will use a red strikethrough to show this. Both Upsource and IntelliJ IDEA will, by default, give a warning when a method marked for removal is used. A reviewer should find out if there’s a valid reason why this method is used, ideally the code author should take the opportunity to use whatever the replacement is.
Java 9’s most famous feature is probably the addition of modularity to the language. If an application has adopted the Java Module System, a code reviewer has another set of things to think about when reviewing changes, particularly if those changes affect the module-info.java file. Using modules gives an additional way to encapsulate our code, code reviewers who understand this can make the most of modules to improve the design and security of their applications.
Java 9 (and, of course, 10 and soon 11) contains features other than modularity, however. Knowing what these are and how and when to use them can help code reviewers to suggest simplifications to existing code. As well as the features mentioned above, there are other additions to the language, like new methods on the Streams API and Optional, these can help to make code more succinct and use a more functional style.
Code reviewers who stay up to date with their language and technology will be able to suggest new features to use in the context of the review. IntelliJ IDEA’s inspections will also often suggest using a new method or approach, making it easier to migrate to the latest version of Java.
Remember, however, that just because something is new doesn’t automatically make it better! Understanding the trade-offs between the old and the new (for example, readability and performance) will let reviewers and authors have productive conversations about the best approach to take in each case.
All the screenshots in this post are from IntelliJ IDEA using the Upsource plugin. Of course, a reviewer may wish to use the Upsource UI instead. Support for Java 9 and 10 Code Intelligence in the web user interface will be available in the next release.
As well as letting you use all the usual analysis and navigation that the IDE gives you, the plugin lets a developer do the review from inside their IDE (all JetBrains IntelliJ IDEA platform IDEs are supported), which may involve a smaller context switch. For a more detailed view of using the plugin, see this video.