DEV Community

Cover image for Java: Write Once, Run Anywhere, But Consult Legal First
Jean-Luc Martel
Jean-Luc Martel

Posted on

Java: Write Once, Run Anywhere, But Consult Legal First

In our previous installment, we explored Java's peculiar relationship with integer equality, collections that refuse to behave like collections, and the remarkable amount of engineering involved in concatenating two strings.

But those were merely quirks of the language.

The Java ecosystem is something else entirely.

Because somewhere along the way, Java stopped being just a programming language and became an entire civilization.

Complete with competing governments, religious disagreements, competing implementations, licensing disputes, and an astonishing number of products named after coffee.

If you think choosing a programming language should be the difficult part, Java has some news for you.

1. First, Let's Figure Out What Java Actually Is

This should be straightforward.

Java is a programming language.

Except Java is also commonly used to describe a runtime platform, a collection of libraries, a development kit, and an entire ecosystem of technologies that don't necessarily involve writing Java at all.

Let's meet the family.

Java: The programming language.

JVM: The Java Virtual Machine, which executes Java bytecode.

JRE: The Java Runtime Environment, traditionally the package needed to run Java applications.

JDK: The Java Development Kit, containing the compiler, runtime, and development tools.

OpenJDK: The open-source project providing a reference implementation of the Java platform and the foundation for many JDK distributions.

Simple enough.

Except the JVM isn't necessarily written by the same organization that distributes your JDK.

And a JDK doesn't have to use the same JVM implementation as another JDK.

And the JVM can execute programs written in languages other than Java.

And modern Java deployments often ship custom runtime images rather than requiring a separately installed JRE.

Already, we've managed to turn "install Java" into an architectural discussion.

But don't worry.

It gets better.

2. One JVM to Rule Them All. Except There Isn't One.

Java's original promise was revolutionary.

Write your program once. Compile it into bytecode. Run that bytecode anywhere a compatible JVM exists.

Which naturally raises an interesting question.

Whose JVM?

HotSpot: The Default Suspect

HotSpot is the familiar JVM implementation used by most mainstream OpenJDK distributions.

It's mature, highly optimized, and has spent decades becoming terrific at figuring out which parts of your code deserve special attention.

Its just-in-time compiler can optimize frequently executed code based on what actually happens at runtime.

This is one reason Java applications can perform impressively well despite running inside a virtual machine.

But HotSpot isn't the only game in town.

OpenJ9: Because One Virtual Machine Wasn't Enough

Eclipse OpenJ9 is an alternative JVM implementation with its own approaches to garbage collection, memory management, and compilation.

It's used in IBM Semeru Runtimes.

The interesting part is that OpenJ9 can run ordinary compatible Java applications.

Same language.

Same bytecode.

Different virtual machine implementation.

Depending on the workload, switching JVMs can change memory consumption, startup characteristics, garbage collection behavior, and performance.

Which means Java developers can spend hours tuning code, only to discover that changing the underlying JVM produces a bigger improvement.

Or makes everything worse.

Benchmark first. Celebrate later.

GraalVM: What if We Compiled the Whole Thing?

Then there's GraalVM.

GraalVM builds on HotSpot and provides the Graal compiler, an advanced just-in-time compiler written in Java.

Yes, Java has a compiler written in Java that helps Java run faster.

But GraalVM's especially interesting trick is Native Image.

Instead of running your application on a JVM at deployment time, you can compile it ahead of time into a native executable.

Potential advantages include faster startup and lower resource consumption.

Particularly attractive for certain cloud and serverless workloads.

Of course, ahead-of-time compilation brings different tradeoffs, including build complexity and restrictions around some forms of dynamic behavior.

So now Java can run on a JVM.

Or on a different JVM.

Or on a HotSpot-based runtime with a different optimizing compiler.

Or as a native executable that doesn't require a JVM at runtime.

Write once. Run anywhere. Spend three weeks deciding how.

3. Oracle Java Licensing: The Plot Twist Nobody Requested

For decades, Java was associated with Sun Microsystems.

Then Oracle acquired Sun in 2010.

And eventually, Java licensing became really interesting.

Not necessarily interesting in the fun programming-language sense.

More in the "why is procurement suddenly attending our architecture meetings?" sense.

Let's walk through the major developments.

2019: Wait, Java Isn't Free Anymore?

In April 2019, Oracle changed the licensing terms for certain Oracle Java SE releases and updates.

Some commercial production uses of newer Oracle JDK updates now required paid licensing where earlier terms had allowed broader use without a separate fee.

That created considerable confusion.

Because developers had spent years treating Java as something they could download, install, and run without thinking much about licensing.

Suddenly, organizations had to examine exactly which Java distribution they were using and under what terms.

But here's the important distinction:

Java the language did not suddenly become a paid product.

OpenJDK remained open source.

Alternative distributions remained available.

The licensing changes concerned specific Oracle-provided binaries and their terms.

Java itself hadn't disappeared behind a paywall.

Nevertheless, the confusion was understandable.

2021: Good News! It's Free Again. Sort Of.

Oracle introduced its No-Fee Terms and Conditions license, commonly abbreviated NFTC, with JDK 17.

This permitted free use, including commercial production use, for eligible Oracle JDK releases under those terms.

Excellent.

Problem solved?

Not exactly.

The free-use period for updates to an Oracle JDK long-term-support release doesn't necessarily last for that release's entire support lifetime.

Later updates can move to different licensing terms.

For example, Oracle JDK 21 updates through September 2026 were available under NFTC.

Beginning with the October 2026 update cycle, subsequent Oracle JDK 21 updates moved to the Oracle Technology Network licensing model, which doesn't grant the same unrestricted free commercial production rights.

Meanwhile, Oracle JDK 25 remains available under NFTC, with its own future transition schedule.

So if you're using Oracle JDK, you need to consider not only the Java version but also the specific update release and its license.

Because apparently version numbers weren't already complicated enough.

2023: How Many Employees Do You Have?

Then came Oracle's Java SE Universal Subscription.

Introduced in January 2023, this subscription moved to employee-based licensing.

Rather than simply counting installations or processors, the subscription model generally considers the size of the organization, according to its contractual employee definition.

That raised an obvious question.

What does the number of people working in accounting have to do with how many Java applications you're running?

Well, that's a question for the licensing department.

Oracle has since updated its pricing, including new small-business tiers introduced in October 2026. Its commercial offerings also include support, updates, and management capabilities.

And to be clear, organizations don't universally need an Oracle subscription to run Java commercially.

You can use appropriately licensed OpenJDK distributions.

But now the phrase "Which Java are we using?" can have financial consequences.

A programming language has successfully turned runtime selection into a procurement strategy.

Impressive, really.

4. Everyone Has Their Own Java

Thankfully, Java's open-source ecosystem provides plenty of alternatives.

You can choose from several OpenJDK-based distributions, including:

  • Eclipse Temurin, from the Adoptium project.
  • Amazon Corretto.
  • Azul Zulu.
  • Microsoft Build of OpenJDK.
  • BellSoft Liberica.
  • Red Hat builds of OpenJDK.
  • Oracle's own OpenJDK builds.

Most of these use HotSpot.

Some vendors offer additional runtime technologies or support arrangements.

And many are compatible implementations of essentially the same Java platform.

So why so many?

Different organizations provide different combinations of update schedules, supported platforms, packaging, long-term maintenance, commercial support, and optimization.

These differences can matter enormously for enterprises running thousands of applications.

But for developers trying to install Java on a new laptop, the experience can feel a little absurd.

You wanted a compiler.

Instead, you've been asked to evaluate a supply chain.

And then someone suggests SDKMAN! to manage all your JDK installations.

Which is genuinely useful.

But also rather funny.

We've created a tool to help manage the consequences of having so many tools.

5. The JVM Doesn't Actually Care Whether You Write Java

Here's where the ecosystem becomes much more interesting.

The JVM executes bytecode.

It doesn't particularly care which programming language produced that bytecode.

And developers have taken full advantage of that.

Kotlin: Java, but With Less Java

Kotlin was developed by JetBrains and has become particularly prominent in Android development.

It offers concise syntax, null-safety features, extension functions, and strong interoperability with Java.

You can call Java libraries from Kotlin.

You can call Kotlin code from Java.

You can even mix the two within the same project.

Kotlin also targets JavaScript, native platforms, and WebAssembly, so it isn't exclusively a JVM language.

But its JVM compatibility is a major reason for its success.

It offers developers a different language without requiring them to abandon the enormous Java library ecosystem.

Which is a polite way of saying:

"I like everything Java has built. I'd just rather not write so much Java."

Scala: What if Java Had a PhD?

Scala combines object-oriented and functional programming with a sophisticated type system.

It supports advanced abstraction, immutability, pattern matching, and a programming style very different from traditional Java.

It's also deeply connected to Apache Spark, which we'll get to shortly.

Scala can be extremely expressive.

It can also produce code that feels like a graduate thesis escaped into production.

The language is powerful enough to let you create abstractions that are beautiful, mathematically elegant, and almost completely incomprehensible to whoever maintains them next.

Groovy: Java After a Few Drinks

Groovy brings dynamic language features and scripting conveniences to the JVM.

It's closely associated with parts of the Java tooling world, particularly Gradle's traditional build-script DSL.

Groovy can use Java libraries and integrate with existing Java code.

Which means you can add a more flexible scripting language without leaving the ecosystem.

Clojure: For the Parentheses Enthusiast

Clojure brings a Lisp dialect and a functional programming philosophy to the JVM.

Immutable data structures.

Persistent collections.

Functions and sequences.

And parentheses.

Lots of parentheses.

Clojure demonstrates the JVM's flexibility particularly well.

You can write something that looks almost nothing like Java and still take advantage of Java's runtime and libraries.

The JVM has become a place where programming languages with wildly different philosophies agree to share the same infrastructure.

If only their developers could agree on indentation.

6. Apache: Where Java Became Infrastructure

Now let's leave ordinary applications behind.

Because Java's influence extends far beyond enterprise web development.

A remarkable amount of modern data infrastructure grew out of the Java and JVM ecosystem.

And much of it lives under the Apache Software Foundation.

Hadoop: The Elephant in the Data Center

Apache Hadoop helped popularize large-scale distributed data processing.

Its ecosystem included HDFS for distributed storage, MapReduce for processing, and YARN for resource management.

The appeal was simple.

Take large datasets.

Spread them across multiple machines.

Process them in parallel.

Make the system resilient when individual machines fail.

Simple in concept.

Considerably less simple when you're actually operating a cluster.

Suddenly your data pipeline has dozens of configuration files, services that need to discover each other, and an impressive collection of logs.

But Hadoop was enormously influential.

And it helped lay the foundations for much of the modern big-data world.

Spark: Python Developers, Meet Your JVM

Apache Spark emerged as a more flexible distributed processing engine.

Its core is closely tied to Scala and the JVM.

But Spark supports Java, Scala, Python, SQL, and, with some qualifications depending on version, R.

This creates a particularly entertaining situation.

A data scientist writes PySpark code in Python.

Spark coordinates distributed execution using its JVM-based engine, with Python worker processes involved where necessary.

So a Python application can be orchestrating work through a Java-based distributed computing platform without the developer writing a single line of Java.

Java has successfully infiltrated the Python ecosystem while allowing Python to take the credit.

To be fair, that's a pretty good arrangement for everyone involved.

Kafka: Everything Is an Event Now

Apache Kafka provides distributed event streaming.

Applications publish events.

Other applications consume them.

Events can be retained, replayed, and processed by multiple consumers.

Kafka became central to many event-driven architectures, data pipelines, and streaming systems.

It's powerful infrastructure.

It also has its own terminology, operational model, and configuration universe.

But there's one historical detail that deserves special attention.

Kafka spent more than a decade relying on another Apache project.

ZooKeeper.

7. ZooKeeper: Challenge Accepted

Distributed systems have a fundamental problem.

Several machines need to cooperate.

Machines fail.

Networks become unreliable.

Nodes disagree about who's responsible for what.

Someone needs to coordinate the chaos.

Enter Apache ZooKeeper.

ZooKeeper provides distributed coordination primitives for tasks such as naming, configuration management, synchronization, and leader election.

For years, Kafka used ZooKeeper to help manage cluster metadata and coordination.

Which meant that to run your distributed event streaming platform, you also needed a separate distributed coordination service.

You now had a cluster to manage your cluster.

And that cluster needed its own monitoring, configuration, and operational care.

At some point, the infrastructure diagram began to resemble a family tree drawn by someone with a grudge.

But here's the plot twist.

In March 2025, Apache Kafka 4.0 eliminated its ZooKeeper dependency entirely.

Kafka moved fully to KRaft, its own Raft-based metadata management architecture.

So after years of operating an additional distributed system to coordinate Kafka, the Kafka project finally said:

"Perhaps we could do this ourselves."

ZooKeeper itself remains a useful project for other distributed systems.

But Kafka's departure is a nice illustration of how the Java ecosystem evolves.

First, build a powerful framework.

Then build supporting infrastructure.

Then build infrastructure to manage that infrastructure.

Then spend several years simplifying the whole thing.

Progress!

And we haven't even discussed Apache Flink, HBase, Hive, or the wider collection of projects that helped turn Java into the plumbing of big data.

8. The Great Java IDE Wars

Surely, with all this complexity, at least the development environment is straightforward?

Of course not.

Java has an impressive history of competing IDEs.

And for a long time, choosing one was practically a declaration of personal identity.

NetBeans: The Old Guard

NetBeans has a long history, including years under Sun Microsystems and later Oracle.

It's now an Apache project.

It offers an integrated Java development experience with editors, debugging, refactoring, project tools, and GUI development capabilities.

For many developers, NetBeans was their introduction to serious Java programming.

A comfortable place to write code.

A reminder of a time when an IDE could feel like one reasonably coherent application rather than a marketplace for extensions.

Eclipse: Your IDE Is Also a Platform

Eclipse took a different approach.

Its plugin architecture allowed developers to extend the environment extensively.

Java Development Tools.

Refactoring.

Debugging.

Build integration.

Language support.

Application development frameworks.

And approximately seventeen different perspectives, views, and preferences screens.

Eclipse was enormously important to enterprise Java development.

It also introduced many developers to the experience of installing one plugin, discovering that it required three others, and then spending an afternoon resolving version compatibility.

Your IDE needed dependency management.

Which, in fairness, was excellent preparation for Java development.

IntelliJ IDEA: What if the IDE Actually Understood Your Code?

Then there's IntelliJ IDEA from JetBrains.

IntelliJ built a strong reputation around code analysis, refactoring, navigation, and developer productivity.

It also offers first-class support for Kotlin, which makes sense given that JetBrains created the language.

For many developers, IntelliJ became the preferred environment for modern Java development.

Although the perpetual debate over which IDE is best remains alive and well.

As with most religious disputes, empirical evidence is rarely invited to the discussion.

VS Code: Fine, Let's Make Everything an Extension

And naturally, Java development is also supported in Visual Studio Code.

Install the appropriate extensions, configure the JDK, set up your build system, and off you go.

Which means the Java IDE wars now include a lightweight editor that can be transformed into something resembling an IDE through plugins.

We've come full circle.

The IDE became a platform.

Then the editor became a platform.

And everyone continued arguing about keyboard shortcuts.

9. Maven, Gradle, and the Art of Building a Build System

Before we finish, we need to talk about building Java applications.

Because writing code is only half the battle.

The other half is convincing your dependencies to cooperate.

Maven: XML Will Save Us

Apache Maven brought standardized project structures, dependency management, and build lifecycles to Java.

It was a major improvement over manually downloading JAR files and assembling mysterious classpaths.

But Maven also embraced XML configuration.

A lot of XML configuration.

You could have a beautifully organized project whose build file was longer than some of its source files.

Still, Maven provided consistency.

And that's valuable.

Gradle: What if Our Build Files Were Programs?

Gradle offered a more programmable build system, supporting Groovy and Kotlin DSLs.

More flexibility.

More control.

Potentially less XML.

Naturally, this also meant build scripts could become arbitrarily sophisticated programs with their own bugs.

Because sometimes a build configuration isn't complicated enough until you can debug it.

Scala developers may also encounter sbt, adding another build tool to the mix.

And underneath much of this sits Maven Central and the wider ecosystem of artifact repositories, transitive dependencies, version conflicts, and the occasional library whose presence in your application nobody can explain.

Java didn't invent dependency hell.

But it certainly helped industrialize it.

10. And Then There's Enterprise Java

We could stop here.

But that would mean ignoring a particularly entertaining corner of the ecosystem.

Enterprise Java.

Java EE.

Jakarta EE.

Spring.

Spring Boot.

Hibernate.

Quarkus.

Micronaut.

And decades of arguments about dependency injection, annotations, XML configuration, and whether a sufficiently abstract factory should require another factory.

Java EE eventually moved from Oracle-led stewardship to the Eclipse Foundation, becoming Jakarta EE.

And because software history enjoys a good practical joke, the transition also involved moving many APIs from the javax.* namespace to jakarta.*.

Which sounds simple until you're migrating an application with years of dependencies.

Meanwhile, Spring built an enormous ecosystem around enterprise application development, and newer frameworks such as Quarkus and Micronaut pursued approaches tailored to modern cloud deployment.

All useful technologies.

All with their own philosophies.

All capable of inspiring passionate architectural disagreements.

At this point, the Java ecosystem contains enough competing approaches to solving enterprise application development that you could spend an entire career evaluating frameworks without ever deploying an application.

Although that might itself qualify as enterprise architecture.

Java's Greatest Strength Is Also Its Greatest Joke

After all this, it's tempting to conclude that the Java ecosystem is a confusing mess.

And in some ways, it is.

Multiple JVM implementations.

Numerous JDK distributions.

Oracle licensing policies that require careful attention.

Languages ranging from Kotlin to Clojure.

An enormous collection of Apache infrastructure projects.

Competing IDEs.

Competing build tools.

Competing application frameworks.

Competing opinions about nearly everything.

But here's the remarkable part.

Much of it actually works together.

A Kotlin application can consume Java libraries.

A Scala-based Spark engine can process workloads submitted through Python APIs.

A compatible Java application can move between different OpenJDK distributions.

A large Java codebase can survive decades of changes to operating systems, hardware, runtimes, tools, and deployment environments.

That's not a trivial achievement.

The JVM became one of the most successful common execution platforms in software history precisely because it allowed an extraordinary amount of innovation to happen around a relatively stable foundation.

And that flexibility created competition.

Competition created alternatives.

Alternatives created choices.

And choices created endless arguments.

Which brings us back to Java's famous promise.

Write once, run anywhere.

Thirty years later, we've added a few practical qualifications.

Write once.

Choose a JDK distribution.

Check the license.

Select a JVM.

Pick an IDE.

Choose a build tool.

Resolve your dependencies.

Decide which framework you believe in.

Configure your deployment environment.

And then...

Run anywhere.

Top comments (0)