Java-bashing is a popular past-time and I’m no exception, yet Java is the top programming language at the moment by many measures. Regardless of whether one thinks that Java is now growing, stagnating, or dying, one has to admit that Java had truly revolutionized and had defined the programming of XXI Century as we know it. Java had combined and brought to the masses tons of useful programming language features that were previously available only separately or in various niche languages.
However, Java had gained a strong foothold in desktop application development and became the language of choice for enterprise backend development.
Managed memory and desktop
The rise of Java desktop was logical in retrospect. Development of big applications had always relied (and still relies) on statically typed languages to such an extent that the term “dynamically typed language” is often conflated with the concept of “scripting language” and reserved for casual, small-scale, or fast-turnaround development.
Java’s strongest competitors for desktop development at the time were C++ and Delphi, yet Java was the only language in the game with memory safety and automatic garbage collection. This proved to be crucial for developer productivity, especially for the event-based style of UI that was prevalent at the time. Java programs were less error-prone to write in large teams. All kinds of reference and array-related failures in Java programs were universally easier to identify and to troubleshoot. These safety features of Java are not free in terms of CPU and memory consumption, yet it is not a show-stopper, but a gift for big desktop applications.
Cross-platform with advanced JIT
Java is compiled to cross-platform bytecode that is later executed on the target machine. This offers a significant advantage for the development of cross-platform libraries and tools ecosystem, as library authors do not have to worry about compilation for every current and future target architecture, yet they can still distribute some kind of “binaries”, instead of source. It is important for business/enterprise software and libraries which are often proprietary.
Bytecode was a common practice that programming languages employed back then, though often to the big detriment to performance. However, Java’s execution had quickly progressed from bytecode interpretation to advanced just-in-time compilation, to the point where performance of wide classes of algorithms had become competitive with state-of-the art C++ optimizers. This had defined the truly universal nature of Java. Over time most of the core algorithms in Java runtime library, that were written in C/C++ in its 1.0 version, were rewritten in Java itself, without losing (and often gaining) in performance.
Profile-guided optimization in the C/C++ world is still cumbersome to use, yet in the Java world, with the introduction of HotSpot around the turn of the century, it essentially became available for the masses in a hassle-free way, only for the cost of an additional startup time.
Application servers and dynamic loading
Java’s ability to dynamically load code, while maintaining rich object-oriented binary-compatible APIs, was initially conceived for the “interactive web”, yet proved to be invaluable for the backend. The very term “application server” had become synonym with the “Java application server”. Application servers ruled the backend world until the emergence of virtualization and containers that had paved the shift to micro-services architectures.
Java’s foothold on backend, surprisingly for modern observer, was also a function of its cross-platform offering. Linux was not a dominant backend operating system and x86 was not a dominant processor architecture back then. There was quite a variety, especially in the business world. The was no REST and even SOAP was just introduced. There was CORBA before SOAP, but it was just as heavy and it did not become as pervasive as REST/JSON now. So, whenever you had to integrate with somebody else’s API it usually meant using proprietary 3rd party code.
Finding binaries that you can use on your system was always quite a hassle in the C/C++ world. Even if you could convince your business partners to compile them for your target, any bugs there would irrevocably crash your code. Java’s business integration story was so much more compelling.
IDEs and refactoring
Java had enabled and created the rich Integrated Development Environments (IDEs) as we know them. Don’t get me wrong. IDEs had existed before for many different languages and they were written in many different languages, too. Yet IDEs, being big desktop UI applications with many plugins, had benefited immensely from the Java’s combination of static typing, managed memory, dynamic code loading, and cross-platformness. Modern IDEs, like IntelliJ IDEA and others based on IntelliJ platform, contain almost ten million lines of code and more than ten years of legacy. The big “IDE wars” of XXI Century between IDEA, Eclipse and Netbeans were and are fought between Java-based desktop IDEs. This had created a feedback loop where Java IDEs had allowed to scale up development and, in turn, create even more complex and feature-rich IDEs.
Around the turn of the Century, Martin Fowler published the Refactoring book on improving the design of existing code. It added a new word to the software developer’s vocabulary. IDEs had caught up and implemented fully automated refactoring support, ultimately revolutionizing the way software was written to begin with. It used to be the case that you had to think through the details and the structure of your code in excruciating detail in advance, since a failure to foresee the need to extract a certain piece of logic into a function or a class, badly chosen name or some other abstraction failure, would lead to costly, monotonous and utterly non-fun rework later on. Not any more. With IDE-supported refactoring you start writing your code in top-down fashion, introducing abstractions and renaming them as you discover the need for them. I’d say that this was one of the greatest improvements in software developer productivity of the XXI Century.
Java was uniquely positioned to reap the most benefits of automated refactoring by a pure chance. The original Java language design did not include any kind of macro system nor preprocessor because of the desire for simplicity and cross-platformness. These decisions turned out to be gold and made Java language exceedingly well suited to safe and automated refactoring in IDE.
The sum is greater than the parts
It is a combination of all those “right things”, some of which I’ve mentioned, that quickly moved Java to the prime spot among the programming languages. Other languages tried to replicate Java’s success, like Microsoft’s C#, which inherited almost all of Java’s strengths. However, it initially locked its users to Microsoft platform, failing to recognize the importance of cross-platformness that Java offered. That was a costly mistake, among some others, that took years to realize and had considerably undermined C#’s potential.
The software development landscape is changing. The areas that Java had captured and revolutionized are being transformed. The desktop development is shrinking to professional and enterprise software, while mass-market consumers exceedingly rely on web and mobile applications. The backend is being reshaped by micro-services. Yet Java and its ecosystem are not going to become irrelevant any time soon. Moreover, Java’s immense impact on the software engineering is going to stay in the languages that will come to dominate the landscape some day in the future.