LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

The Story of Java

javajvmoraclekotlinprogramming-languagescomputing-historybytecode

Java is the most successful programming language almost nobody set out to build. It was designed in the early 1990s for interactive television and set-top boxes — a market that did not exist and did not materialize for another fifteen years. It pivoted to the web at exactly the moment the web was being invented, rode the dot-com boom into every bank and insurance company on earth, became the default language of computer science education for two decades, and then, in a final twist nobody planned, became the foundation of Android and therefore the most-deployed application platform in human history. None of that was the original plan. The plan was to make toasters and cable boxes talk to each other. The thing that survived all of those pivots, the thing that is actually the story, is not the syntax. It is the Java Virtual Machine: a portable abstract computer with a defined bytecode, a verifier, a just-in-time compiler, and a garbage collector, whose architecture became so influential that Microsoft cloned the whole idea wholesale for .NET, and whose existence turned out to matter far more than the language that shipped on top of it.

The thesis of this post is that Java won on a bet about where programs should run, not what they should look like. “Write once, run anywhere” was a claim about a runtime, not a syntax, and that claim — portable bytecode plus managed memory — was correct and durable even when the language wrapped around it was verbose, ceremonial, and frequently mocked. The proof is that the JVM outlived Java’s monopoly on it. The same virtual machine now runs Scala, Clojure, Kotlin, and Groovy, and the most exciting work on the platform for the last decade happened in those languages, not in Java itself. This is the history — the 1991-to-now arc. For where the language actually stands today, the records it still holds and the modern features it finally shipped, see the companion piece on Java in 2026.


The Green Project and Oak (1991)

In 1991, Sun Microsystems chartered a small skunkworks called the Green Project, led by James Gosling with Mike Sheridan and Patrick Naughton. The brief was not “build a better C++.” It was to figure out what Sun should do about the coming wave of networked consumer devices — set-top boxes, handheld controllers, the interactive television everyone in the early 1990s was certain was about to arrive. These devices ran a zoo of incompatible processors, they had tiny memory budgets, and they could not tolerate the kind of crash a stray pointer caused. C and C++ were the obvious candidates and both were poor fits: too unsafe for embedded reliability, and fatally non-portable, because a C++ program had to be recompiled and often rewritten for each chip.

Gosling’s answer, originally called Oak (after a tree outside his window, later renamed Java when a trademark search found Oak taken), was shaped entirely by those constraints. The language would be safe: no pointer arithmetic, no manual free, automatic garbage collection, array bounds checking — eliminating whole categories of the bugs that made embedded C terrifying. And it would be portable in a way C never was, by refusing to compile to any real machine’s instruction set at all. Instead it would compile to bytecode for an imaginary machine, the virtual machine, and each real device would ship a small interpreter for that imaginary machine. Port the interpreter once per device and every program ran everywhere. This was not a new idea — UCSD Pascal’s p-code did it in the 1970s, and Smalltalk had bytecode VMs — but Sun executed it at a scale and with a marketing budget nobody had brought to it before.

The set-top box market never came. The Green team built a working handheld device (the “Star7”) and pitched it to the cable industry, which was not buying. By 1994 the project was adrift, a brilliant technology in search of a problem. The problem it found was the one nobody on the team had been hired to solve.


The Pivot to the Web (1995)

The web happened. Mosaic shipped in 1993, Netscape in 1994, and suddenly there was a global network of heterogeneous machines — Windows PCs, Macs, Unix workstations — all wanting to run the same content, none of them able to run the same binary. This was the set-top-box problem at planetary scale, and Sun had a solution sitting on a shelf. Java was repositioned almost overnight from “consumer electronics language” to “the language of the web,” and the killer demo was the applet: a Java program embedded in a web page, downloaded as bytecode, verified for safety, and run inside the browser on whatever machine the user happened to have. Netscape Navigator bundled a Java runtime in 1995, and “write once, run anywhere” became the slogan of the decade.

Applets, in retrospect, were a dead end — slow to start, awkward in the page, a security nightmare, and eventually killed off entirely. But they did their job: they got Java onto millions of machines and into the imagination of every developer who had ever cursed a cross-platform build. The real prize was never the browser. It was the server. By the late 1990s, the same portability and safety story that justified applets turned out to be exactly what enterprises wanted for back-end systems, and Java’s center of gravity moved decisively to the server side, where it has stayed for thirty years.

The slogan also acquired its famous shadow. “Write once, run anywhere” ran headlong into the reality of subtly different JVM implementations, AWT/Swing GUI quirks, threading differences, and version skew, and developers rechristened it “write once, debug everywhere.” The promise was real but it was never total. What was genuinely portable was the core — the bytecode, the object model, the standard library’s non-GUI parts — and that core was portable enough to change the industry even though the edges leaked.


What the JVM Actually Is

To understand why Java mattered, you have to look past the language at the machine underneath it. The execution pipeline looks like this:

   Foo.java  (source)
      |
      |  javac  (compiler)
      v
   Foo.class  (portable bytecode + constant pool)
      |
      |  loaded at runtime
      v
+---------------------------------------------------------------+
|                  Java Virtual Machine (JVM)                    |
|                                                               |
|   ClassLoader  --->  Bytecode Verifier  --->  Interpreter     |
|   (finds &           (proves type- &          (runs cold      |
|    links .class)      stack-safety)            bytecode)       |
|                                                  |            |
|                                                  | hot paths  |
|                                                  v            |
|                                   JIT (C1/C2 HotSpot) --> native|
|                                                               |
|   Garbage Collector (G1 / ZGC / Parallel) manages the heap     |
+---------------------------------------------------------------+
      |
      v
   native machine code on x86-64 / ARM64 / ...

Take a trivial class:

1
2
3
4
5
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, JVM");
    }
}

You compile and run it in two steps, and the intermediate .class file is the whole point — it is the artifact that is portable, not the source:

$ javac Hello.java        # produces Hello.class (bytecode)
$ java Hello              # the JVM loads, verifies, runs it
Hello, JVM

Disassemble the bytecode with javap -c and you can see the stack machine the JVM actually is — operands are pushed and popped, methods are invoked by symbolic reference into a constant pool:

$ javap -c Hello
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7   // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #13  // String Hello, JVM
       5: invokevirtual #15  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

Four steps make this more than a clever interpreter. First, the class loader finds and links classes on demand at runtime, which is what makes Java dynamically extensible — frameworks load classes that did not exist at compile time, and the whole plug-in/application-server world depends on it. Second, the bytecode verifier statically proves, before a single instruction executes, that the bytecode is type-safe and the operand stack never underflows or overflows — this is the safety guarantee that let untrusted applets run in a browser at all, and it is a genuinely deep piece of engineering. Third, the JIT compiler — Sun’s HotSpot, donated to the project in 1999 — watches the program run, identifies the “hot” methods that actually consume time, and compiles those to optimized native code while leaving cold code interpreted. Because it optimizes with real runtime profile data (which branches are taken, which types actually show up), a long-running JVM program can match or beat statically compiled C++ on some workloads, a fact that still surprises people who assume “interpreted” means “slow.” Fourth, the garbage collector manages the heap automatically, and the JVM’s GCs are among the most sophisticated ever built — modern low-pause collectors like ZGC hold pause times under a millisecond on multi-terabyte heaps:

$ java -XX:+UseZGC -Xmx16g MyServer

That bundle — portable bytecode, runtime linking, provable safety, profile-guided JIT, and automatic memory management — is the actual invention. The language was the wrapper; the VM was the breakthrough.


The Influence: Managed Runtimes Everywhere

The JVM’s success made a specific architectural argument so convincingly that the rest of the industry simply adopted it: compile to a portable, verifiable bytecode for a managed virtual machine, and let a JIT and a garbage collector handle the messy parts. Before Java this was a research idea and a niche (Smalltalk, p-code). After Java it was the default for new application platforms.

The most direct descendant is Microsoft’s .NET and its Common Language Runtime, shipped in 2002. The CLR is, structurally, an answer to the JVM: it has its own bytecode (CIL), its own verifier, its own JIT, its own GC, and it was explicitly designed to host multiple languages (C#, F#, VB.NET) on one runtime. The parallels are not coincidental — there is a direct and litigated history here, since Microsoft had shipped a non-conforming Java (J++) and was sued by Sun over it before pivoting to build its own thing. Beyond .NET, the managed-runtime model shaped how everyone thought about safety and portability: that you could trade a little raw speed for memory safety, dynamic loading, and platform independence, and that for the vast majority of business software this was an excellent trade. This was the opposite philosophy from C, which deliberately stayed close to the metal and made the programmer responsible for memory; Java proved there was an enormous market that wanted the machine to handle it instead. The dynamic scripting languages — most prominently Python — made the same managed-memory bet from a different direction, trading even more performance for even more programmer convenience.


J2EE, Enterprise Bloat, and the Spring Backlash

If the JVM is Java’s triumph, the enterprise stack of the early 2000s is its cautionary tale. As Java conquered the server, Sun and a committee of vendors built out J2EE (Java 2 Enterprise Edition): a sprawling specification of application servers, Enterprise JavaBeans (EJB), JNDI, JMS, and a culture of XML configuration files that grew to rival the code in size. The intentions were reasonable — distributed transactions, persistence, remote objects, security — but the execution became a byword for over-engineering. To write what amounted to a database-backed form, an EJB 2.x developer wrote a remote interface, a home interface, a bean implementation, and multiple XML deployment descriptors, then deployed the whole thing into a heavyweight application server. The ceremony-to-value ratio was absurd, and the phrase “enterprise Java” became an industry insult for needless complexity.

The backlash produced something better. Rod Johnson’s book and the Spring Framework (2003) argued that most of the J2EE apparatus was unnecessary, that dependency injection and plain old Java objects (POJOs) plus a lightweight container could deliver the same outcomes without the application-server cathedral. Spring won, comprehensively, and dragged the whole ecosystem toward simplicity. The standards bodies eventually caught up — EJB 3 and later annotations-based configuration absorbed Spring’s lessons, and the JCP itself was eventually superseded by the Eclipse Foundation’s Jakarta EE after Oracle handed the enterprise specs over in 2017. The episode is worth remembering for two reasons: it is the origin of Java’s enduring “verbose and bureaucratic” reputation, and it is proof that the community could route around a bad official direction. The verbosity criticism was earned. It was also, in large part, eventually answered.


Generics and the Erasure Compromise (2004)

Java 5, in 2004, added the other feature most associated with the language’s awkwardness: generics, with a design decision called type erasure. The constraint was backward compatibility. Sun had millions of lines of existing code and a runtime full of un-generified collections, and the decision was made to add generics purely in the compiler — the type parameters are checked at compile time and then erased, so that at runtime a List<String> and a List<Integer> are both just List. The bytecode and the VM did not change.

This was a genuine engineering trade-off with real costs. You cannot write new T[], you cannot ask if (x instanceof List<String>), and primitives cannot be type parameters, which is why Java has both int and Integer and an entire boxing/autoboxing dance. C#, arriving a year later and without the same legacy weight, chose reified generics where the runtime knows the actual type parameter, avoiding many of these warts. Java’s choice has been second-guessed for twenty years. It was, however, defensible: erasure preserved binary compatibility with the entire existing ecosystem and required no changes to the verifier or the bytecode format, and the alternative would have splintered the platform. It is a perfect miniature of Java’s whole governance philosophy — protect the installed base above all, even at the cost of elegance.


Sun’s Decline and the Oracle Acquisition (2010)

For all of Java’s success, Sun Microsystems never figured out how to make much money from it. The language was free, the JDK was free, and Sun’s hardware-and-Solaris business — its actual revenue — was being eaten alive by commodity x86 servers running Linux. The 2008 financial crisis finished the job. In a defensive move, Sun open-sourced the JDK as OpenJDK in 2007 under the GPL, a decision that turned out to be enormously consequential for the platform’s long-term health even as it did nothing to save the company. In 2010, Oracle acquired Sun for about $7.4 billion, and with it Java, Solaris, MySQL, and the rest.

The acquisition was met with dread, much of it justified. Oracle was famous for aggressive licensing and litigation, not for stewarding open communities, and the early signs were mixed — James Gosling himself left within months. But Oracle also invested real engineering in OpenJDK, made it the canonical reference implementation, and drove the modernization that finally pulled Java out of its mid-2010s stagnation. The verdict on Oracle’s stewardship is genuinely split, and the single event that defines it is a lawsuit.


Android, released in 2008, used the Java language and reimplemented the core Java APIs on a custom runtime (Dalvik, later ART) rather than licensing Sun’s JVM. Google had reproduced the declarations — the names, signatures, and organization — of 37 Java API packages so that the millions of developers who knew Java could write Android apps, but had written its own implementation of every method. When Oracle acquired Sun, it inherited the Java copyrights and patents and sued Google, arguing that the structure, sequence, and organization of those API declarations was itself copyrightable, and that Google had infringed by copying it.

The stakes were enormous and existential for the whole industry. If the labels and organization of an API — the thing every reimplementation, every compatible library, every clean-room clone reproduces — were copyrightable, then decades of interoperability work were retroactively illegal. The case ground through the courts for a decade with wild swings: a jury found no infringement, the Federal Circuit reversed and held the APIs copyrightable, another jury found fair use, the Federal Circuit reversed that. In 2021, the U.S. Supreme Court ended it in Google’s favor, ruling 6-2 that Google’s reuse of the API declarations was fair use. Critically, the Court declined to decide whether APIs are copyrightable at all, assuming for argument that they were and then finding the use fair — a narrower ruling than many wanted, but one that protected the practice of reimplementing APIs for interoperability. It is one of the most important software-law decisions ever rendered, and the fact that the language at its center was Java is a measure of how central Java had become.


OpenJDK and the New Cadence

The other half of Oracle’s stewardship was structural, and it genuinely revitalized the platform. For most of its life Java shipped major versions slowly and unpredictably — there were multi-year gaps, most notoriously the long drought between Java 6 (2006) and Java 7 (2011) during the Sun collapse. In 2017, with Java 9, Oracle moved to a strict six-month release cadence: a new feature release every March and September, no matter what. Most of those releases are transient, but every few years one is designated a Long-Term Support (LTS) release that vendors back-port fixes to for years — Java 8, 11, 17, and 21 are the LTS milestones the industry actually standardizes on.

Version Year Status Landmark feature
Java 1.0 1996 historic Applets, AWT, the original VM
Java 5 2004 historic Generics (erasure), annotations, autoboxing
Java 6 2006 historic Performance era; then a 5-year drought
Java 8 2014 LTS Lambdas, streams, the functional turn
Java 9 2017 feature Modules (JPMS); start of 6-month cadence
Java 11 2018 LTS First LTS under the new model; var matured
Java 17 2021 LTS Sealed classes, pattern matching preview
Java 21 2023 LTS Virtual threads, records, pattern matching

Alongside the cadence came a licensing shake-up that confused everyone for a few years. Oracle split its commercial Oracle JDK from the GPL-licensed OpenJDK and changed the terms under which Oracle’s own builds could be used in production, which sent the market scrambling to vendor-neutral OpenJDK distributions — Eclipse Temurin (Adoptium), Amazon Corretto, Azul Zulu, Microsoft Build of OpenJDK, Red Hat. The practical upshot is healthy: there is no longer a single gatekeeper for a production-grade Java runtime, and OpenJDK is a genuinely open, multi-vendor project. But the licensing whiplash did real reputational damage and remains a live source of confusion. For how all of this plays out in current practice — which JDK to actually run, what the modern language feels like — the Java in 2026 post is the place to go.


The JVM Language Family

The most quietly important thing about the JVM is that it was a good enough compilation target for languages that are nothing like Java. Once your language compiled to JVM bytecode, you inherited the entire ecosystem for free: the JIT, the GC, the colossal library of existing Java code, and deployment everywhere a JVM already ran. That gravity well spawned a family of languages, and for roughly the last decade the most interesting language design on the platform happened in them rather than in Java.

                    JVM bytecode
                         |
   +---------+-----------+-----------+-----------+
   |         |           |           |           |
  Java     Scala      Clojure      Groovy      Kotlin
 (1995)   (2004)      (2007)       (2003)      (2011)
  OOP +   OOP +       a Lisp,      dynamic     pragmatic
  imper.  functional  immutable,   scripting   statically-
          fusion,     STM, data-   & Gradle    typed; null
          academic    first        DSLs        safety; Android

Scala (Martin Odersky, 2004) fused object-oriented and functional programming into a single powerful, type-rich language, and became the academic and big-data favorite — Apache Spark and much of the data-engineering world were built on it. Its ambition is the subject of its own deep dive on Scala and functional programming on the JVM. Clojure (Rich Hickey, 2007) put a modern Lisp on the JVM, immutable by default, with a serious story for concurrency, choosing to “embrace a host” rather than fight for its own platform the way every previous Lisp had — its lineage runs straight back through the Lisp story. Groovy (2003) was the dynamic scripting glue of the JVM and became the language of the Gradle build tool. And Kotlin (JetBrains, 2011) was the pragmatist: statically typed, concise, null-safe, fully interoperable with Java, and explicitly designed to fix Java’s verbosity without abandoning its ecosystem. Google’s decision in 2017 to make Kotlin a first-class Android language, and in 2019 its preferred one, made it the JVM’s biggest modern success story — covered in detail in Kotlin for the JVM and beyond.

Polyglot-on-the-JVM mattered because it decoupled the platform’s longevity from the language’s. Java the language could be conservative, slow-moving, and verbose, and it largely was — but the platform stayed at the frontier, because anyone with a better idea about syntax or semantics could implement it as a JVM language and ship it into the same ecosystem the next day. The VM was the moat. The languages were interchangeable tenants on top of it.


Verdict

Java’s staying power is real, and it is not nostalgia — it is the predictable payoff of a single correct bet made under odd circumstances. Gosling’s team, trying to make set-top boxes reliable and portable, built a safe language on top of a portable, verifiable, garbage-collected virtual machine, and that runtime turned out to be the right abstraction for an entire era of computing: for the web’s heterogeneous clients, for the enterprise’s long-lived servers, and finally for the most successful mobile platform ever shipped. “Write once, run anywhere” was always more aspiration than guarantee — “write once, debug everywhere” was the honest version — but the portable core was portable enough to remake the industry, and the JVM’s architecture was influential enough that Microsoft rebuilt it from scratch as .NET. Three decades in, Java runs the banks, the trading systems, the airline reservations, the telecoms, and through Android the phone in nearly every pocket.

The criticisms are equally real and worth stating without flinching. Java is verbose, and for years it was defensively verbose — the J2EE era was a genuine festival of ceremony, the erasure compromise on generics left permanent scars, and the modern language is still catching up to conveniences that Kotlin and Scala had a decade earlier. Oracle’s stewardship has been a study in contradictions: it sued Google for a decade over the very interoperability practice that made software ecosystems work, lost at the Supreme Court, and yet simultaneously poured the engineering and the release discipline into OpenJDK that finally modernized the platform after years of drift. The most damning honest observation is that the most exciting things to happen to “Java” in the last fifteen years mostly did not happen in Java — they happened in Kotlin, in Scala, in Clojure, on the VM Java built and then partly ceded. But that is also the deepest measure of the win. Sun set out to build a language and accidentally built a platform that outgrew it, durable enough to host its own successors — a stranger and more impressive legacy than “write once, run anywhere” ever promised. For the present-day state of the language, the 2026 view picks up where this history leaves off.


Sources

Comments