What are some things every Java developer should know?

Core Spring 4.2

The foremost thing every Java developer should know is that the language is not “finished”. After a period of relatively slow development, updates have accelerated in recent years leading to smaller but more frequent feature drops. JDK 11 will be released this year, and each new release carries with it new enhancements, features, updates to the standard library and more. Hence, to me, the most important thing to know is the limit of one’s knowledge (and a desire to expand it).

Having said that, here are some of the things I have learned over the years that have served me well.

1. Get familiar with the Java Class Library

Shipping with the JVM itself, this library is probably the most well-debugged Java library available. Pretty much anyone using Java is debugging it. As such, leveraging it as much as possible is a good start on the way to not have too many bugs.

Having said that, there are quirks, some of which are surprising. My personal favourite, and an item that often flies under the radar, is spurious wakeup, a decades-old artifact that permeates many imperative threaded programming languages. It is documented in depthrepeatedly in the Java documentation, yet I had missed it and most people I introduce to it are unaware of it as it is one of those bugs that mostly only happens on a heavily loaded system and thus is easily missed.

Additionally, it follows from the Java language evolution that so does the JCL. This adds an ongoing — but very useful — effort to update one’s knowledge of the JCL as new features are added. For example, the introduction of Lambda expressions in Java was a nice addition from a syntax point of view, but it was made all the better by the JCL adding new methods to all old interfaces that effectively eliminated the need for many types of common boilerplate, such as Map.computeIfAbsent().

2. Learn how to write documentation properly

One of the most-frequently misunderstood parts of the JCL I encounter with developers is Comparator. This is also one of the most striking ones, as it is ostensibly an extremely simple interface: one function that says if an element comes before or after another element. Let’s have a look at the javadoc for the all-important compare function:

Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.

In the foregoing description, the notation sgn(expression) designates the mathematical signum function, which is defined to return one of -1, 0, or 1 according to whether the value of expression is negative, zero or positive.

The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y. (This implies that compare(x, y) must throw an exception if and only if compare(y, x) throws an exception.)

Actually, it goes on for three more paragraphs, each carving out a more specific use-case, and none of which bothering to provide an example of the more-and-more theoretical description provided.

The end result is that most developers I know of have effectively given up on this documentation and will just run the comparison in a test case to see if they got it the right way around or not (it’s a 50–50 chance anyway).

Compare it, for example, with the documentation for String.substring() which spends far less time being theoretical, ending up providing concrete examples and thus allowing the reader to quickly grasp the essence of the method.

3. Threading is the Dark Side (mostly). See how to avoid using threads explicitly.

Much work has been done on the Java side to provide methods that abstract away threading. Constructs such as ForkJoinPool, implicitly used when using Stream.parallel(), and JCL packages such as java.nio/2 deal with different ways of avoiding to use/create multiple threads.

This trend has been predicated by multiple avenues, both Java and otherwise. For example, RxJava has been preaching this pattern for a while. It’s also one of the reasons for the popularity of NodeJS. There are many advantages of doing it, and it is a testament to the JVM that it can support different approaches, gaining the advantages of them.

4. Don’t try to outsmart the JVM.

Java, the JVM and the JCL have had contributions from many smart people. Yes, some of the parts make little sense, and others have been redone multiple times because of inherent flaws; however, on the whole, a lot of thought has gone into it. It’s possible, though unlikely, to outsmart it.

An excellent source on this is the JVM Anatomy Park, a strongly recommended read on how the JVM does cool and smart stuff under the hood for you. The one I’ll choose to highlight (but there are more, and do have a look at them all!) is lock elision. In short, it’s a way for the JVM to completely eliminate synchronized blocks that it determines don’t perform useful locking. As a developer one can therefore write correct code while the JVM makes sure it is performant.

A related issue would be the synchronized keyword. A younger, more naïve me would come armed with the theory of locking and figure out a situation where a Spinlock would be better than a Mutex. That’s great and all, until one figures out (after not seeing any performance improvement) that Java will automatically use both spinlock and mutex for synchronized depending on how it is performing.

Of course there are still cases for judicial use of synchronisation and reasons for when to do what. But outsmarting the JVM is almost never a good reason to write code.

5. Java has loads of tooling, for a good reason. Leverage it!

Perhaps one of the biggest benefits that come with Java as such an established language is the veritable cornucopia of tools associated with it, from static code analysis, to runtime configuration, to convention libraries and pretty much everything under the sun.

Any good Java developer today should know how to leverage that code. Doing this has two benefits.

First, it makes it easier to debug code, find errors before they reach production, find them afterwards and optimise the code. There is no reason to make these tasks anything but easier.

Second, and far more dear to my heart, is that leveraging all this existing code makes one’s own codebase all that much smaller. Assuming the tooling chosen is good (e.g. Spring) then that ideally means less code, less bugs.

Leave a Reply

Your email address will not be published. Required fields are marked *