Getting cozy with Java's new, softer side

New features like var, auto-compile, text blocks, record classes, and more are shifting Java in subtle but powerful ways—toward a more flexible, dynamic future.

Getting cozy with Java's new, softer side
Tom Wang/Shutterstock

Java has made some big shifts over the past few years, as seemingly disparate forces converged to make the platform easier to use. New features like auto-compile and the var keyword lower the bar for using Java, for beginners and veterans alike. Let's take a look at what's cooking in this new, friendlier Java.

var

Perhaps the most astounding thing about modern Java, at least for long-timers, is the presence of var support. One of Java’s defining characteristics is that it is strongly typed, but var loosens that a bit. Within a method, you can now define a reference using var where the compiler will keep track of the type for you.

After much hand-wringing about the wisdom of introducing this feature, Java developers everywhere have simply adopted it like it is the most obvious thing ever. Didn’t Java always let us do this?


var numbers = new ArrayList<Integer>();

No, it didn’t!

Hidden compilation

This new feature almost feels like running a source file without compiling it, but what's happening under the hood is a little more complicated. The .java file is still compiled, but JEP 330 makes it so it’s all hidden from you. So you can now do:


$ java HelloWorld.java

Instead of


$ javac HelloWorld.java
$ java HelloWorld

The runner compiles the source in memory and executes the first main class it discovers.

JEP 330 is limited to a single source file but hang on, because here comes JEP 458.

Auto-compile multiple source files

JEP 458 joins up with JEP 330 to let developers run multiple source files with hidden compilation. When a Java source refers to another, the Java launcher will compile the dependency in memory, load it, and provide it. In short, you can run entire programs of interrelated source files, so long as they exist on disk together, without an explicit compile step.

Is it me, or is Java evolving into something more dynamic? This is very cool.

A better build toolchain

The introduction to JEP 458 says it aims to:

Enhance the java application launcher to be able to run a program supplied as multiple files of Java source code. This will make the transition from small programs to larger ones more gradual, enabling developers to choose whether and when to go to the trouble of configuring a build tool.

This says in plain language that Java is working toward simplifying the lives of developers through better builds. The JEP even goes so far as to say that it is "not a goal to ease the use of external library dependencies in source-code programs," although "that may be the subject of a future JEP" (italics mine).

I understand here that Java's developers are working on making the tooling easier to use, even down to the dependency level—finally! Maven is still my go-to build tool for complex projects, as it has been for 15 years, but the tool shows serious signs of age. (A new archetype project in Maven picks Java 1.7 as its default!) Compared to NPM, Maven is downright clunky; a real impediment to getting projects off the ground quickly—especially for beginners who have to learn the build tool while also learning Java.

A platform-ordained dependency manager could be just the thing. When I look at JEP 330 and JEP 458 together, I see not piecemeal band-aids to tooling usability, but the thoughtful rolling out of an integrated, in-platform toolchain. What is emerging is a Java toolchain designed to make it easy to start a project and incrementally adopt more sophisticated tooling as needed.

JShell

I know, JShell has been around for a long time now, but let’s acknowledge how nice it is to have a REPL-style runner for Java. Remember to use JShell to try out new ideas on the fly.

Simple Web Server

One day we woke up and Java had a command-line web server. You can learn all about Java's Simple Web Server here.

The key takeaway is: if you are using Java 18 or higher, your JDK install now includes a dead simple and easy way to serve files. As a result, the Java platform is a bit more one-stop, integrated, and complete. I have previously used Python or Node for basic file-serving needs; now, I can just do it all in Java.

Nested text blocks

Another feature Java developers have long envied in other platforms was an easy way to handle big chunks of text in code. JEP 378 brings the text blocks we've been looking for:


String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

I don't know about you, but that block gives me a sigh of relief.

Text blocks can also nest quote characters freely:


String html = """
              <html>
                  <body>
                      <p>Hello, "This is a quote"</p>
                  </body>
              </html>
              """;

Record classes

The product of JEP 395, the record keyword, lets you create a POJO (plain old Java object) without manually adding getters, setters, toString, equals, and hashcode methods as you normally would. When you use record, the platform adds all those niceties for you:


class Point {
    private final int x;
    private final int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    int x() { return x; }
    int y() { return y; }

    public boolean equals(Object o) {
        if (!(o instanceof Point)) return false;
        Point other = (Point) o;
        return other.x == x && other.y == y;
    }

    public int hashCode() {
        return Objects.hash(x, y);
    }

    public String toString() {
        return String.format("Point[x=%d, y=%d]", x, y);
    }
}

Becomes:


record Point(int x, int y) { }

The new record classes also allow for a great deal of customization, so you can have as much or as little added as you want.

New and improved switch

With JEP 361, Java’s switch syntax became more powerful and easier to use. This happened in two ways: a cleaner syntax and the ability to use switch as an expression and not just a statement.

The switch now makes it possible to take clunky old syntax like this:


switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        System.out.println(6);
        break;
    case TUESDAY:
        System.out.println(7);
        break;
    case THURSDAY:
    case SATURDAY:
        System.out.println(8);
        break;
    case WEDNESDAY:
        System.out.println(9);
        break;
}

And turn it into something like this:


switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
    case TUESDAY                -> System.out.println(7);
    case THURSDAY, SATURDAY     -> System.out.println(8);
    case WEDNESDAY              -> System.out.println(9);
}

Equally awesome, you can use it as an expression to variable assignment (which is often all we are after, anyway):


int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    case THURSDAY, SATURDAY     -> 8;
    case WEDNESDAY              -> 9;
};

Structured concurrency

Now, concurrency is never going to be a happy-go-lucky area of coding. There is something inherently mind-bending about working with multiple threads. Nevertheless, structured concurrency gives you possibly the easiest way to handle concurrency in the known coding universe:


try (var scope = new StructuredTaskScope<Object>()) {
  for (int planetId : planetIds) {
    scope.fork(() -> getPlanet(planetId)); // fork a “process”
  } 
  scope.join();
} catch (Exception e){
  System.out.println("Error: " + e);
}

When combined with Java’s new virtual threads, structured concurrency is a superpower among languages. Now, you can easily use concurrency in Java, without spawning operating system threads, and without leaving normal programming constructs.

Conclusion

Platform developers are working on multiple fronts to lower the bar for developers working in Java. They are improving the day-to-day lives of working coders and making it easier to get started with Java and experiment with it.

These efforts are especially impactful in that peculiar pocket of the developer world that I’m going to call “prototyping,” otherwise known as “playing around.” The more we can do to reduce the distance between thinking about trying something and actually trying it, the better. 

It’s great to see Java in such good shape. I like the direction it's moving toward and the people leading those efforts.

Copyright © 2024 IDG Communications, Inc.