Java is almost unique in being able to run code from 25 years ago on a modern version of Java. The language developers take backwards compatibility very seriously, and because of this many organisations are happy to make Java their primary development platform, knowing that the code will still run on the JVM for years to come.
There are plenty of advantages to having been around for a long time. For the last 25 years, developers have been writing applications in Java for a huge range of industries and business types, and for different platforms. Meanwhile, over these 25 years, developers have been learning Java in schools, universities, boot camps, and on the job. This has created a large ecosystem that has had time to learn from experience and continues to grow. Java, and the problems it can solve, is well documented and well supported by vendors and non-profit organisations and individuals. Very importantly for developers like us, the maturity and broad adoption of Java means there are plenty of jobs for developers who want to code in Java!
In contrast to backwards compatibility and maturity, is the evolution of the platform and the language. Since 2017 (Java 9) there’s been a new release every six months, which has enabled a steady stream of changes and improvements to the language. Combined with Preview Features, the language is able to experiment with new syntax, get feedback from developers, and then standardise new features that really work well for those who use the language.
Java has a difficult balance to strike between backwards compatibility and embracing the future. The current approach of valuing backwards compatibility and releasing every six months, but with a Long Term Support release every three years, seems to strike the right balance. The language is able to evolve by providing warnings for deprecated features that will be removed, and by having replacements for anything that might go away.
Those who wish for extra stability can stay on a Long Term Support release. To reduce their risk when it does come time to upgrade, they could be regularly testing against every new release. Those who are happy to upgrade every six months can update to the latest version of Java every time it comes out. Developers who want to try out new syntax even before it’s standardised can enable preview features, and those who want to do so as soon as possible can even use the Early Access release of a version that’s not out. Teams using modern versions of Java really can have the best of all worlds.
Standards might not be as exciting to a developer as language features, but having standards for Java, for Java EE and Jakarta EE, and for common use-cases that developers run into, does make a developer’s life easier. Understanding how to use JDBC to talk to a database means that we don’t have to care about how the database driver is implemented, the way we interact with it is always the same. The JCP is one of the processes used to determine standards for Java.
The Java Language Specification covers what Java-the-language looks like and how the compiler should work. It includes the Java Memory Model, which can help us to predict how an application might behave, regardless of JVM or hardware. The Java Virtual Machine Specification covers the lower-level details down in the JVM. These specifications enable JDKs distributed by different vendors, running on different platforms, to behave in specified, predictable ways. Which leads us to…
WORA was one of the original ideas behind Java, which seems so obvious these days that we might not even realise how ground-breaking it was. I remember working for a very, very large organisation who, back in 2002, switched from their previous technology stack to Java simply because they had a lot of different hardware lying around, and being able to run new Java applications on it instead of having to buy specific hardware for applications was one of the main reasons they moved all their development to Java. In this day and age of Cloud this might seem less relevant, but in fact just because we don’t always see WORA in action does not mean we’re not still taking advantage of it. And of course, if you’re using IntelliJ IDEA (or NetBeans) you’re taking advantage of WORA on the desktop.
It sometimes comes as a surprise to those who are less familiar with Java, but Java is a very high-performance language. It’s a mature platform that has 25 years of performance improvements; numerous garbage collectors with different performance profiles; and the JVM optimises our code at runtime for our real production use-cases far better than most human developers ever could. Java is used extensively in the finance world, for example, which depends on low-latency transactions and predictable performance.
Automatic Garbage Collection is something else that, 25 years on, we generally take for granted. We don’t have to think about how memory is allocated in our applications, or how to free it. Each JVM has one or more different GC algorithms, so we can pick one that works well for our application without having to concern ourselves too much with the internals, we can just get on with writing the business logic for our applications.
If we are interested in what’s going on while our application is running, there are a huge number of tools available to us. Many of them are even free, particularly since Java Flight Recorder and Mission Control are now part of OpenJDK (since Java 11). Tools like JMX even allow us to dynamically manage our applications too.
Many of the features we’ve just mentioned are features of the JVM, but we specifically want to call out the JVM, and the fact that it’s separate from Java-the-language. There are many reasons to love the JVM, including some of the things we’ve already covered: WORA, runtime optimisations, performance, vendor choice, etc., much of which is made possible because of standards and specifications. It’s important that the JVM is separate from Java-the-language, too. It means different languages can be built upon the platform, taking advantage of all the great features of the JVM we’ve just mentioned while providing different types of syntax and language features.
One of the reasons Java survived, even thrived, in those quiet years between Java 6 and 8 (Java 7 has some great features but it didn’t feel like a big release for Java developers) is because of the other JVM languages. At JetBrains of course our favourite is Kotlin, but the JVM is a platform for other popular languages like Groovy, Scala, Clojure and JRuby, and a huge number of other new and re-implemented languages. In many cases, interoperability between these languages and classic Java helps us to embrace and leverage this variety.
One of the most compelling arguments is the huge amount of choice of libraries and frameworks we have, many of which are Open Source and free to use. Popular frameworks like Spring, and Spring Boot in particular, make it easy to create anything from small services to large and complex enterprise applications. Standards mean that often it’s not hard to understand and use a library when we’ve been using something similar in another context. The maturity of Java, and the community’s early adoption of open source, means that there’s usually an existing solution for standard problems; there’s no need to re-invent the wheel. It also means that since many of these solutions have been around, and in use, for a long time: they’re well tested, well understood, and well documented.
Long gone are the days when a poor developer had to search on the internet for some unknown JAR file containing some class that the code they are trying to run apparently requires. Maven and Gradle, in particular, made it easy to build and deploy an application, of course, but also to set up a project in a standard way with all the required dependencies. It’s straightforward to start coding in either a new or existing project. Public repositories like Maven Central and Bintray give us well-known places to find (and publish) libraries.
JUnit was created in 1997 so is nearly as old as Java itself! It is by far the most common automated testing framework in the Java world, and both JUnit and TestNG are shipped with IntelliJ IDEA since it’s assumed a testing framework will be needed with any new Java project. It’s likely that modern testing frameworks for a whole range of languages are based on the ideas we now take for granted from JUnit. The Java community’s culture of automated testing owes a lot to this one library.
This is the IntelliJ IDEA blog, we weren’t going to forget this one! Whether you believe the verbosity of Java requires an IDE, or that actually Java can really leverage an IDE because of its static typing, the fact is that Java developers love their IDEs (and we love you!). Learning to use an IDE effectively (whether it’s IntelliJ IDEA, Eclipse, or NetBeans) can significantly improve a developer’s productivity, via: code completion and generation, running tests, debugging, navigation, and a bunch of other features. Java developers are usually extremely enthusiastic about the benefits an IDE brings.
The Java Community is a large, mature, vibrant, and welcoming community. There are Java User Groups in many cities worldwide, and a Virtual Java User Group if you can’t get to a physical meetup. The Java Champions are recognised technical leaders in the Java world who became known for sharing things that are useful or interesting for Java and JVM developers. Java has a huge Open Source community, including the JDK itself via OpenJDK. The Java Community values learning, teaching, and constant improvement, cares about standards and “best practice”, and is pragmatic about how to apply these in a real-world environment.
The Community is made up of people, of course, but when I asked what developers value most about Java, many of them specifically called out individuals in the Java world who had impacted them. These range from colleagues and teachers, to people like Brian Goetz, Angie Jones, Georges Saab, Mala Gupta, Venkat Subramaniam. Some people even mentioned me. Personally, I came to Java because I learnt it at university and there were plenty of jobs, but I stayed because I love the people and the help and support I’ve had from them.
Java makes API documentation a key part of the language via Javadoc. The three different types of comments (Javadoc, block and line) clearly indicates what sort of commentary a developer is leaving. Javadoc specifically encourages developers to leave helpful documentation for other developers who call a method, or use a class or package. If a developer cannot find detailed tutorial information on a library or piece of code, there’s usually Javadoc that can help point them in the right direction.
In addition, the Java ecosystem generally seems to expect (and provide) good quality documentation for developers. Prospective committers to open source projects are encouraged to submit pull request of Javadoc comments or other documentation, and developers all over the world ask and answer questions on StackOverflow, or blog about solutions to specific problems.
Not only did the Java community embrace open source early on, but now the JDK itself is open source too via OpenJDK. Open source makes it easier for multiple vendors, and individuals, to be involved and collaborate. It’s also fascinating to be able to look at the code of Java itself. Open source code provides a great opportunity for developers like us to learn from people who have already done all the hard work thinking about and solving complicated problems.
The Java platform and many of the most popular tools used in the Java ecosystem don’t require a developer (or often even a profit-making organisation) to pay money to use them. Even after Oracle changed its licensing and support in Java 11 they (and many other vendors) still provided a way to use the language for free in production. The open source projects, build tools, and IDEs already mentioned in this article are all either free or have a free option. This makes Java appealing for developers getting started with coding, and for organisations of all sizes who need to build software while keeping an eye on the budget.
Object-oriented programming is not the only game in town, of course, and every paradigm has its advantages and disadvantages. Java was designed as an OO language right from the start, and many examples of design patterns and other coding best practice for OO are demonstrated in Java. If you want an object-oriented programming language, Java should be high on your list of ones to try.
Java was, and still is, an object-oriented programming language. But it has also successfully adopted some concepts from functional programming (like lambda expressions and immutable data structures) and made them work nicely within the OO paradigm. Type inference (for example var) allows us to still have the benefits of a statically typed language, but with less boilerplate. Computer science is still a relatively young discipline, yet as we learn things they can be applied to our existing tools. Java (the language and the whole ecosystem) is constantly evolving according to new trends and new “best practices”, and also as a result of seeing how things work out in the real world.
Java also gets to benefit from syntax and ideas from other JVM languages, to see what works and what perhaps might not fit in a classic Java world. Even the processes and tools that Java uses to evolve and grow, such as the JCP and OpenJDK, are also adapting themselves to stay relevant in this constant evolution. This evolution and adaptation is part of the careful balance Java has to strike.
Java code is often readable, even for non-Java programmers. The language leans towards being more verbose rather than over-concise, which makes it easier to reason about Java code when we’re reading it. The language developers have not implemented features like operator overloading, as they feel it’s important to not be surprised by unexpected custom behaviour. There’s a tendency to avoid “magic” in the language and the frameworks – although some frameworks will use things like Convention over configuration to do things without a developer having to be imperative about it. There’s definitely been a move away from,for example, doing lots of AOP with annotations, and more towards using annotations for documentation and static analysis checks. The community and ecosystem tend to like standards and “best practice”, so Java code often follows similar sorts of rules even on very different projects.
We’ve covered 23 Things We Like About Java but haven’t mentioned a single feature! To be honest, it’s because limiting ourselves to just 25 features seemed extremely difficult, and also because lots of the things we love about Java are not about the syntax or features. We’d like to give an honourable shout out to some of developers’ favourite features:
- Collections API: it’s been with us a long time and served us well!
- Convenience factory methods for collections: makes creating unmodifiable collections so much easier.
- Streams API: a very nice addition to the collections API, and great to see it evolving even since Java 8. Parallel streams added a new way to utilise modern hardware.
- Lambda Expressions: particularly useful with the Streams API, but very handy in its own right.
- Optional: a nice way to express that a method might not give you something (and stops us having to protect against NullPointerExceptions!). Really nice to see each improvement to Optional since Java 8 as well.
- java.time: the latest API for working with date and time is a welcome improvement
- Checked exceptions: people are divided on checked vs runtime exceptions, but at least checked exceptions are there for those who wish to use them.
- Annotations: annotations are like (among other things) documentation that the compiler can check, or notes for a framework to do some heavy lifting.
- JShell: now we can play with Java in a REPL
- var: type inference helps reduce code noise if used sensibly
- Access modifiers and modularity: Java makes it easy (even more so since Java 9) to be explicit about which code can access which fields, methods, classes, and modules.
- Switch expressions: now if we use switch it’s a lot less ugly!
- Helpful NullPointerExceptions: who doesn’t love a good NullPointerException? Now the exceptions give developers much more useful information about what was null.
- Preview features: we love preview features! We’re very excited about Records; Pattern matching for instanceof; and Text Blocks.