11 reasons the new Java is not like the old Java

Java is not the language it used to be, and that is mostly a good thing. Here are eleven ways Java is evolving to meet the challenges of the future.

11 reasons the new Java is not like the old Java
Nathan Dumlao (CC0)

Is Java ancient yet? The kind of programming language used by old timers who prattle on about front panels with blinking lights and the days of floppy disks? Or is it still hip, with all the latest language enhancements for intuitive coding and top-notch performance? Maybe Java is somewhere in between: a mature language, but young at heart.

Close to 30 years ago on May 23, 1995, Java officially entered the world. It began as an enabling technology called “Oak” for a set-top box that Sun Microsystems imagined would soon colonize the American living room. That plan didn't work out, not at first, anyway. But the language grew into one of the core foundations for modern software, running on everything from tiny sensor chips to large server boxes.

Since then, the idea of Java has changed dramatically. Sun and Oracle have done a remarkable job of grafting on features that keep the language feeling fresh without disturbing much of the core functionality. Maybe Java is just one of those languages that keeps going.

One thing we know for sure is that many of the features living inside the big tent called “Java” are different from what was originally envisioned—often radically so. Programmers are creating code that people in 1995, 2005, or even 2015 wouldn’t recognize, but the old code still runs. That’s a high-quality curation. Oracle, the company that bought Sun in 2010, is now delivering new versions regularly and adding features that keep the Java language relevant.

Here are eleven ways that Java has changed, mostly for the better.

10 ways Java programming is better than it used to be (and one reason it's not)

  1. Virtual threads
  2. Structured concurrency
  3. Immutable data
  4. Garbage collection
  5. Pattern matching
  6. Streamlined syntax
  7. Sealed classes
  8. Foreign functions and memory
  9. The Vector API
  10. Improved null processing
  11. Free as in ... paid license?

Virtual threads

The original version of Java gave developers the opportunity to create their own Thread objects and control how the code ran in multithreaded and multicore environments. While it was better than nothing, programmers quickly learned that Thread objects were rather large and took too much time to create and destroy. Creating a permanent thread pool at the start of a program became a common workaround for clunky threads.

All that changed in Java 19, with the arrival of virtual threads. Now, the JVM handles much of the work of doling out system resources in Java programs. Programmers specify when parallelism is available and the runtime JVM runs the code concurrently where it can. Virtual threads are a boon to modern architectures like microservices, which are easier to develop and support.

Structured concurrency

Lighter-weight threads are just the beginning. Java is adding an abstract model for parallelism that will make it easier for programmers and the JVM to process workloads concurrently. The new structured concurrency model offers the programmer the chance to break a Java workload into tasks, which are then grouped into scopes. Scopes are collected into fibers that work together in the same thread.

The goal is to offer Java developers a standard, boilerplate model for building parallel programs, so they don’t need to reason deeply about it each time. Structured concurrency also makes it easier for the virtual machine to detect opportunities for concurrent execution and map them to processor cores.

Immutable data

In the beginning, Strings were fixed in stone. Once a String was created, it  could never be changed. Calling a function like toLowerCase would create an entirely new String. This made it simpler for the JVM to manage security and synchronization across Threads.

Now, Java programmers can specify the same immutable rules for their own objects by calling them “Records.” This simplifies multi-threaded safety, caching, and synchronization. The code lists the names and types of the fields and the JVM handles the rest. Common methods like equals, hashCode, and toString are created automatically. The rest of the time, the JVM ensures that the Records are immutable, which simplifies many program details and speeds up the running code.

Garbage collection

Java has always handled many of the details of memory allocation and reclamation, a feature that many programmers are happy to delegate to the JVM. Sometimes, though, the original garbage collector would pause long enough that users would notice when performance lagged.

Today, programmers have a choice between four garbage collectors, which use a variety of different garbage collection algorithms and are specialized for different types of applications:

  • The Garbage First (G1) Garbage Collector is the default choice, delivering better throughput with shorter pauses. G1 employs techniques that have grown out of lessons learned from earlier iterations of Java garbage collection, like shuffling the biggest blocks and finely tuned recognition of frequently changed small objects.
  • The Z Garbage Collector is designed to be very low latency, a requirement for web servers, streaming services, and other real-time data work. It can also do well with a very large heap because it was designed to scale to 16 terabytes of RAM.
  • The Concurrent Garbage Collector will run in the background without stopping the application at all. It’s best for work like interactive applications that shouldn’t ever pause, although it may not be as efficient.
  • Finally, the Parallel Collector uses multiple threads to collect data faster, but the stops are more unpredictable.

Developers aren't stuck with a single method for garbage collection, and don't have to resort to other solutions like simulating their own memory management by reusing objects. There are now four major choices and each one provides options for even more tuning and experimentation.

Pattern matching with switch

The Java team has also enhanced the language at some of the lowest syntactic levels, giving developers more options for writing cleaner, more expressive logic. The switch keyword, used to create stacks of if-then-else conditionals, now offers pattern matching, which means that the logic for specifying the various cases isn't limited to basic expressions like equals.

Java code written with these patterns is especially concise, and it’s able to make distinctions not just on the value in the data but the object type. All reference types and the null pointer can be used. Of course, the more traditional logic with fall-through semantics is still supported so old code continues to run smoothly.

Streamlined syntax

In the beginning, writing Java wasn’t much different from writing C or C++. Curly brackets and semi-colons did much the same thing in Java as in C. Loops were structured with the classic three-part form. Although its guts bore a deep connection to Lisp, Java's basic syntax wasn’t much different from C’s.

More recent additions, though, have all borrowed from the simplicity of scripting languages like Ruby and Python. For loops don’t need to spell out every detail because the compiler can now intuit them when you’re looping through a list or an array. Anonymous functions and lambda expressions are also good choices for programmers who want to save keystrokes.

Some of the wordiness and excess punctuation of C are still there, but today's Java programmers can spell out complex structures with fewer characters.

Sealed classes

From the beginning, the JVM was designed to prevent many common security holes that programmers might leave in their programs by mistake. The newest versions have added even more options. Sealed classes, for example, allow the class creator to specify exactly which classes can extend it. That prevents someone else using a library from extending a class and adding or overriding some of the original functionality.

Sealed classes also run a bit faster than traditional ones because they allow more aggressive optimization and inlining. They can also simplify method dispatch.

Foreign functions and memory

The Java virtual machine was designed to be a walled garden or a typesafe sandbox. The virtual machine guards the code and prevents many general attacks that are possible when code runs natively.

For programmers in the know, the original Java Native Interface (JNI) was a bit of a backdoor. The Java team knew that some developers needed to connect to libraries and stacks written in other languages, and that some system calls were essential. So they opened up this hole in the JVM's armor, with a simple warning about the dangers of using it.

Now, we have the Foreign Function & Memory API, currently a JEP in third preview. This API would make it easier and safer to connect with the outside. Much more of the work can now be written in pure Java, opening up opportunities for regular Java programmers to start connecting to general system memory. The proposal also adds better guardrails like type checking to block some of the worst potential overflow attacks.

This API would make it easier for Java code to take on more low-level tasks and data processing in system coding. It's a safer way for Java programmers to start breaking out of the sandbox.

The Vector API

The original Vector class, known to many veteran Java programmers, was more of a data structure and less of a mathematical tool. It was a flexible and synchronized solution for stashing objects that wasn’t much different from List.

The new Vector API is much more. It's a tool for the kind of mathematical data processing that’s becoming more common as artificial intelligence algorithms use matrices and vectors in more or less the same way as physical scientists and mathematicians do. The individual elements can be primitive types and many of the basic mathematical operations like dot products are supported.

A good way to see the difference between the Vector class and API is to see what the add method does. In the original class, it just stuck an object at the end of the data structure like all of the other Collections classes did. In the API, it’s used to mathematically add the individual elements, more like an engineer would expect.

The Vector API also promises to open up the massive computational powers of some newer SIMD processors, enabling Java programmers to craft code that can churn through many long vectors.

Better null processing

Is that object a null pointer? Much Java code is devoted to checking, double-checking, and then triple-checking objects. To simplify the code and speed things up a bit, Java has slowly been adding features that handle null pointers in a more graceful way. The Stream API, for example, can process long streams of data and won’t get hung up if an occasional null value comes along. The Optional class wrapper may or may not hold an actual object, allowing the code to flow nicely. And if you still want to check for nullity, there’s the null-safe operator (?.) that tests for null in a very concise way.

Free as in ... paid license?

Java has always been pretty much free, at least for programmers. From the beginning, Sun wanted to attract developers with free tools and hardware, and in 1997 the company took the bold step of open sourcing many parts of the language and its virtual machine. Until recently, developers could more or less write once and run anywhere without paying a dime.

Now, the picture is getting murkier. Many Java versions from Oracle are free but some require a license with strange terms. It would seem that Oracle wants programmers to enjoy the freedom to create without monetary constraints, but also wants to extract a tax or rent from the enterprises that generate significant, long-term revenues from Java. In practice, this means charging for what Oracle calls Java's subscription features. So, Java is still free, unless you want to upgrade it for commercial use.

Copyright © 2024 IDG Communications, Inc.