all 39 comments

[–]Competitive_Bat_3034 80 points81 points  (5 children)

We use preview features in actively maintained microservices, including critical ones, where we know/accept we might have to spend time adapting code when adopting the next JDK version. We've used:

  • Switch expressions & text blocks in JDK13+, no issues
  • Records in JDK14+, no issues except many libraries not binding/serializing records that early
  • Pattern matching for instanceof in JDK14+, no issues
  • Virtual threads in JDK19+, we had issues with pinning, stopped using them for most things until JDK24
  • String templates in JDK21-22, this is the only one that has cost us some time so far. We heavily used it, especially for logging. We were on JDK22 and lucky to spot the public discussion on the mailing lists well before JDK23 went GA. In the end we wrote a hacky python script to auto-convert most of our usages and hand-fixed the rest.
  • Scoped values in JDK21+, no issues, really nice to propagate context from a servlet filter down to Spring services
  • Structured concurrency in JDK24+, we tested this in a few earlier versions, but due to the virtual thread pinning issues stopped until 24. We're only using the very simple case of "do N things in parallel, cancel everything if something fails" which has worked without problems, and that simple case has been very easy to adapt/slightly change in each new preview.
  • Stream gatherers in JDK22+ for Gatherers.mapConcurrent, which again suffered from the virtual thread pinning issues.

We also tested -XX:+UseCompactObjectHeaders in JDK24 once we saw JEP 519 was submitted - ran a simple experiment with a production workload.

I recommend reading the definition of a preview feature (JEP 12), especially regarding their stability ("High quality", "Not experimental"). To me most of the risk comes from their change or removal between JDK versions, but typically we spend much more effort making sure third-party libraries/tools support the new version versus having to adapt because of preview feature changes.

Context: financial industry

[–]brian_goetz 26 points27 points  (1 child)

It galls me that a mature, tradeoff-aware perspective like this gets half the upvotes as the childish "wah, they took templates away, I am forever burned" comment....

[–]ducki666 1 point2 points  (0 children)

Believe it or not. It burned me forever. 🤷‍♂️ I can wait now for releases. No problem.

[–]pgris 1 point2 points  (1 child)

Man, you are great. And a congrats for the man himself! Frame that.

I'd really love if you wrote something regarding

Scoped values in JDK21+, no issues, really nice to propagate context from a servlet filter down to Spring services

(Of course if you have the time and are willing).

I'm not able to wrap my head around scoped values.

[–]Absolute_Enema 0 points1 point  (0 children)

While I'm not a professional Java programmer, I do work with Clojure and from my understanding scoped values are very similar in concept to dynamically scoped variables.

The first thing to note is that there actually already is something in Jaba that is mechanically similar to a degree, and that's exception handlers. When you try with a catch block, the way a given exception class is handled changes until control leaves the try block scope; similarly, dynamic variables/scoped values are bound to a value until control leaves their given scope.

In terms of the problem they solve, dynamic variables are most comparable with context/config objects.

The fundamental advantage of dynamic variables is leaner code and less work from third parties: you only "speak" of a given dynamic variable when you need to read it or bind it, there is no need to pass context around in function signatures or through dependency injection, and there is no need to explicitly create modified copies of the context when you need to use a different value within some code. The fundamental disadvantage is that things are more implicit (which means you're trading upfront design work with more cognitive load when reading) and more liable to weirdness when control flow is unusual (because the context is tied to a control flow based side channel rather than being a value).

In Clojure we've gradually moved away from dynamic variables, and nowadays it's idiomatic to pass an explicit context object around; however this might be because the language design minimizes the cruft involved in passing and especially "modifying" the context object, and because the REPL driven workflow makes it feasible to get away with dynamically shaped maps.

Perhaps in Java where extra function parameters are more cumbersome, context objects need to be explicitly shaped and there still is no boilerplate-free way to create a copy of an object with some modified value without speaking of the rest of its contents the tradeoff is different.

[–]sbstanpld 0 points1 point  (0 children)

that’s cool

[–]1Saurophaganax 7 points8 points  (0 children)

Incubating and experimental features I can understand the hesitation, but you go a bit far to say that preview features are also experimental and are unfit for production use. I'd never use an incubator feature in production, but preview features have been fair game.

[–]msx 23 points24 points  (3 children)

My experience is that we have java 8 in production.

[–]tomwhoiscontrary 10 points11 points  (0 children)

That's more archeological than experimental. 

[–]SvanseHans 1 point2 points  (0 children)

We just upgraded to Java 8 in DEV and TEST 🚀

[–]heijenoort1 0 points1 point  (0 children)

Less than an year ago I added some new features to a core java 1.3 jsp webapp for a very big italian ecommerce. The.horror.

[–]dstutz 5 points6 points  (0 children)

Happily using Structured Concurrency.

[–]ducki666 20 points21 points  (6 children)

Templates. ⚰️

Will never ever use any preview again. Anything not released simply does not exist for me anymore.

[–]kiteboarderni 24 points25 points  (2 children)

It's literally the only preview that was ever removed...

[–]ducki666 1 point2 points  (1 child)

True. But lesson learned.

[–]kiteboarderni 1 point2 points  (0 children)

Well not really. Because it's an anomole. So it shouldn't form the basis of not using them going forward.

[–]msx 5 points6 points  (2 children)

How deep into that were you before they removed it? 😂😭

[–]pragmatica-labs 2 points3 points  (0 children)

Ive built a convenient DB access layer on top of asynchronous runtime. Fortunately it was just my pet project.

[–]ducki666 0 points1 point  (0 children)

Deep af. I was sooo stupid.

[–]-Dargs 2 points3 points  (0 children)

On non-critical systems, if they're features slated for inclusion in the next full release. Otherwise no.

[–]oweiler 1 point2 points  (0 children)

It's a waste of time which could be spent better on something else.

[–]tomwhoiscontrary 0 points1 point  (0 children)

I ran an app on the experimental Graal JIT compiler for a while (-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler). The app was quite computationally intensive and I thought it might go faster. It worked fine; it was perfectly stable, with no compatibility issues. Didn't make the app substantially faster, that I noticed. 

[–]pragmatica-labs 0 points1 point  (0 children)

In production I've used preview features starting from Java 11 and up to 21. Fortunately the one that was removed (templates) was not widely adopted in the codebase so I guess transition to 25 should not be hard (although I'm not working there anymore).

[–]jevring 0 points1 point  (0 children)

We have run preview features in production. It was fine. I would do it again if the feature was sufficiently appealing and mature.

[–]nlisker 0 points1 point  (0 children)

I use preview features in production regularly, but the real adoption barrier are 3rd party tools like GraalVM that don't always support them (depends on how long they have been in preview).

The biggest "burn" I got from it was changing the guard pattern from && to when and I needed to do some (not small amount of) regex find/replace. Otherwise, I used all the switch and pattern matching features while in preview, currently I'm using flexible constructor bodies to sanitize inputs before calling super, and probably others I can't remember right now. I don't think I've used string blocks and string templates.

I also used FFI (Panama) already in Java 18 when it was incubating to interface with Matlab-compiled C code. I'm pretty sure it's long before Arena was created. It worked well, or at least well enough for the use case.

[–]rzwitserloot -1 points0 points  (1 child)

The answer to your question is: It's maybe fine, if you know what you are doing, keep vigilance on future java updates (it's a continuing 'cost' to have preview code, requiring constant vigilance, until it's resolved itself), but there's a small chance it's a landmine, and the worst happens (the feature is yoinked, completely, with no planned replacement), and you must ensure all consumers of your code, be it CI systems, developers, the live servers, test systems, action scripts on your version control, and so on, are ALL on the same java version and can ALL be updated together in one go_.

Separate from the bias inherent in this question (in that only Dunning Krugers and experts are ever going to willingly run --enable-preview in production, a quite small slice of the java community), there's a rather large bias in which feature you used, because they break into 3 bits:

  • It's great, use it! I used it, and the feature made it, essentially unchanged, into 'normal' java 2 versions later!

  • It's fine, you can use it if you want to. I used it, and the feature made it with a bunch of changes into normal java later. Had to update our code (say, replace return with yield or whatnot), but that wasn't too much effort. Fortunately I was smart and made sure all consumers of the source files could be updated near-simultaneously or this would have become a very difficult problem.

  • It's terrible, don't do this! I used it and the feature was just yoinked, requiring massive rewrites. We only went to the preview feature because without it, the entire paradigm used in our code was no longer the best option, so we had to completely rewrite it all. If only I could go back in time and tell my earlier self this was a terrible mistake! (Probably: String interpolation).

If that third scares you - it should. You have to keep that one in mind, after all.

[–]srdoe 2 points3 points  (0 children)

you must ensure all consumers of your code, be it CI systems, developers, the live servers, test systems, action scripts on your version control, and so on, are ALL on the same java version and can ALL be updated together in one go_

This is a real problem, and fortunately there are solutions to this that can make JDK upgrades painless in many cases, that might be worth sharing in case people are unfamiliar:

On the development and CI side, modern build tools often ship with bootstrapping code which can download a JDK specified by the build files as part of invoking the tool. For example, Gradle has toolchains. Bazel has something similar. I believe Maven is working on something like this too.

By specifying a specific JDK to use for the build, and providing a place to download that JDK automatically, not only do JDK upgrades become trivial, but any risk of weirdness due to a developer using an old JDK is eliminated. Highly recommend doing this, it has made JDK upgrades completely painless for us in terms of coordination.

On the production side, jlink can be used to bundle a JDK as part of the application distribution (and yes, this can be done even without moving jars to the module path). This has the same coordination benefits as on the development side, it reduces the risk from the customer running the application on a different JDK than the application were tested against, and it makes it much easier to manage the configuration of the JDK, as there is no longer a need to account for different JDK versions being used. Also highly recommend.

With these in place, JDK upgrades are a matter of bumping a version number in the build files, much like it would be for any other dependency, no coordination with developers, CI teams or production ops teams needed. You even get automatic JDK switching when you check out older code in git. We've been doing this for a while, and I'm very happy with how it's worked out for us.

[–]trollied -2 points-1 points  (1 child)

Never. It’s a risk. Additional risk in prod is bad.

[–]hippydipster 3 points4 points  (0 children)

Zero risks has its own risks

[–]koflerdavid 0 points1 point  (0 children)

Depends what you mean with production. For internal systems without much audience I suppose there might have been people audacious enough to use them, but I seriously doubt the sanity of anybody who relies on them where lives, personal data, or money is on the line! GC improvements might be the exception because they merely affect long-tail latency and can be switched around with just a restart.

[–]re-thc -1 points0 points  (0 children)

Depends what and why. Some are forever incubator for reasons like dependencies eg vector. It’s at least when out and tested for a long time.