Language selector

About Java's Structured Concurrency, Elasticsearch Java client, DevEx and Developer Advocate's job

There’s been quite a lot happening recently, and I wasn’t sure in which order I should tackle the issues, so I decided to cover them all in a single post. If some aspects need clarification or further detail, please DM me, and I’ll try to sort it out somehow (perhaps in a subsequent entry).

Revamp of Structured Concurrency in Java 25

First, let’s start with Java 25 on the horizon. For many vendors (if not all), it’s going to be an LTS release. And Structured Concurrency serves as a great example of why we need preview features. One could say that three preview releases are enough and the feature should be standardized. However, the OpenJDK folks decided otherwise, and I’m very glad they did. I think I’ll skip boring you with all the details about how Structured Concurrency will be shaped in Java 25 in every nitty-gritty detail. IMHO, reading the JEP 505 and watching Nicolai’s video can help a lot here, in addition to my own talks, e.g. Butcher virtual threads like a pr0.

IMHO, it’s great that throwIfFailed is now the default, and therefore not needed, as I found throwing exceptions useful every time I tested this new feature. Then, sealing the StructuredTaskScope and creating new ones with open(), where you can provide auxiliary configuration, is simply great for a number of reasons:

  • It’s not that inheritance is always bad—I think I’ve seen a number of cases where it actually made sense and was the best option (so my point from CONTEXTVS STVLTE, that it’s not wise to always replace inheritance with composition, still stands). However, providing configuration in the form of a subclass doesn’t seem like a good idea to me. When I wrote my custom implementation of StructuredTaskScope (for a different success criterion), it always felt awkward. Also, if the evolution of Stream Collectors and Gatherers taught us something, it’s that for configuration it’s probably better to use composition.
  • For me, try-with-resources—meant to automatically close resources—somehow works better with open. I don’t know, I might be biased, but it just… looks better than a constructor.
  • Sealing StructuredTaskScope seems to be exactly what Sealed Classes (and Interfaces) in Java 17 were meant to be used for: easing maintainers’ lives and making sure the number of “useful custom inventions” stays limited.
  • Being able to (re)use a custom thread pool (because virtual threads might not be the optimal choice for some kinds of tasks) or to configure other aspects is also cool.
  • Last but not least, it also proves the usefulness of the release train concept and preview features: preview features might be okay to try in production as long as they’re used in a microservice1, and you run it in an environment you fully control—but not if you can’t control which JDK version people use to run your artifacts.

So all in all, in Java 25, a block with Structured Concurrency—where a single method is responsible within its scope for handling concurrent subtasks in a way that’s clear to read and understand—may look like this:

try (var scope = StructuredTaskScope.open(
      StructuredTaskScope.Joiner.awaitAllSuccessfulOrThrow(),
          config -> config
          .withName("myStructuredConcurrency")
          .withTimeout(Duration.ofSeconds(5))
          .withThreadFactory(sctf))) {
    var subtask1 = scope.fork(() -> action(...));
    var subtask2 = scope.fork(this::anotherAction);
    scope.join();
    combineResults(subtask1.get(), subtast2.get());
}

The pattern used for configuration emerged independently in another part of the Java ecosystem. Which brings us to the new Elasticsearch Java client ;-)

Elasticsearch 9 from Java’s perspective

After I started working for Elastic, the construction of the Java client was, let’s be honest, one of my pet peeves. It’s not that it wasn’t working—it was perfectly functional, flexible code. In many frameworks you wouldn’t even need to construct the client manually, because the framework would do it for you based on environment variables or similar configuration.

The thing is, sometimes your microservice is written properly and really is a micro one, not bloated with tons of dependencies. In such cases, you end up with many mandatory lines just to create the ES client in vanilla Java, passing effectively only two things: the URL and the API key. Not the optimal signal-to-noise ratio, if you ask me.

Thankfully, the people I work with are not only clever, but they also listen to fellow developer advocates and implement fixes based on the feedback they hear. That’s why Elasticsearch version 9.0 came equipped not only with many great internal features, but also with a new client version, which allows us to write this:

try (var esClient = ElasticsearchClient.of(c -> c
    .host(System.getenv("ES_URL"))
    .apiKey(System.getenv("ES_API_KEY")))) {
    // use the esClient as you need
}

I’m curious what you think about this. IMHO this is a great change:

  • The ElasticsearchClient can finally be constructed in just a few lines, where each line is actually meaningful—not just boilerplate string concatenation.
  • All the underlying machinery uses default implementations, unless you explicitly provide your own (for valid reasons).
  • Last but not least, it’s also auto-closeable, so after the try block ends, the client is properly closed, resources are released, and your program exits cleanly—no need for System.exit() (which we really shouldn’t use anyway).

How to name it?

As the old wisdom says, there are two big problems in Computer Science:

  • naming
  • cache invalidation
  • off-by-one errors.

We're looking for a name for this pattern -- where configuration is done with a lambda that transforms a configuration object.
Turns out, constructing closeable objects using factory methods with configuration passed via lambda expressions is gaining popularity. Personally, I find it neat and useful—better than passing configuration via objects, implementing interfaces (with all those default methods), using subclasses, or even the classic builder pattern. However, as indicated in a tweet by Brian Goetz, we might not have a proper name for this particular pattern.

So if you have a proposal, perhaps you could reply to Mr. Goetz’s tweet?

What does it have to do with developer advocacy?

I think I should write a separate entry about being a developer advocate—what it’s like, what the pros and cons are, what we do and what we don’t do (especially since I’ve been asked this question on several occasions).

In short, I’d say: we don’t sell, and we don’t act as propaganda officers. What we do (among other things) is representing fellow developers from the wild world within the organisations we work for. We take the best there is to offer, and we present it using the best means we can. In other words, our job is not just to broadcast from the inside out, but also to act as a two-way relay: transmitting expectations, lessons learned, and so on, from the outside in—even if that is frequently overlooked.

In this very case, I was able to bring feedback “from the community” and express the need for changes internally. That’s exactly why you should follow Sharat Chandler’s advice to attend the Hallway Track at conferences (and annoy developer advocates—they might be more effective than filing issues in public trackers ;-) )

DevEx!

Oftentimes this refers to Developer Experience, sometimes abbreviated as DevEx. You see, even when you create a high-performing piece of technology—even a true gem of software engineering—if people don’t know how to use it, or if it makes them feel clumsy, your job isn’t done. The best piece of technology is one that does all the above and doesn’t require a PhD in reading manuals ;-)

Conclusion

Happy 30th Birthday Java!
That having been said, I’m happy that my colleagues from the Dev Tools team made this great change, showing that getting started with Elasticsearch may now require only three or four lines. I’m also happy that Java’s ecosystem, despite turning 30, is still evolving—accommodating new ideas in a huge ecosystem, and doing so without breaking backward compatibility.

Java is still kicking!


  1. A microservice is a service that can be completely rewritten by your team in max. 2 weeks; if the rewrite from scratch takes longer, it’s not a micro service, just a service. Definition by me and my friends. ↩︎

Language selector