Tales from the jar side: A rant about Java LTS versions, Scary Awesome AI, and the usual silly social media posts
What happens when a microscope crashes into a telescope? They kaleidoscope (rimshot)
Welcome, fellow jarheads, to Tales from the jar side, the Kousen IT newsletter, for the week of June 29 - July 6, 2025. This week I taught my first Claude Code course on the O’Reilly Learning Platform.
A Rant About LTS Versions
Nicolai Parlog, a Java Developer Advocate at Oracle, published a video this week on the official Java YouTube channel.
In case you can’t see it, the extremely clickbait-y title is Java 25 is ALSO no LTS Version: Inside Java Newscast #94. In case you don’t want to watch it, here’s a summary from Claude Sonnet 4:
Main Argument
Java 25 (like Java 21 before it) is not inherently an "LTS version." Instead, individual JDK vendors choose to offer long-term support for specific JDK distributions.
Key Distinctions
Support vs. Updates/Maintenance:
Support means a commitment to fix bugs and provide staff to answer problems (costs money)
Updates/maintenance refers to code patches and security fixes (can be free)
Java vs. JDK:
Java 25 is a specification, not a binary
JDK 25 is the actual implementation that vendors build and distribute
OpenJDK vs. Vendors:
OpenJDK develops the reference implementation but doesn't ship binaries
Vendors create distributions and decide their own support policies
The Reality of "LTS"
Different vendors offer varying support timelines, even for the same Java versions. Some observations:
Non-"LTS" versions (like 13, 15) have received multi-year support from certain vendors
"LTS" versions get different support durations from different vendors
Java's feature development is completely independent of LTS considerations
Why This Matters
Parlog argues that misunderstanding LTS can:
Create confusion when seeking actual support for production issues
Make it harder to choose appropriate JDK distributions
Allow bad actors to exploit the gap between perception and reality to harm the Java ecosystem
Bottom Line
The level of support you receive depends entirely on which vendor's JDK you use and what contract you have with them, not on whether a Java version is labeled "LTS."
My opinion is that this is complete and utter bullsh*t. Let me explain, but first I’ll give you the opportunity to skip all this and go to the next section if you are not in that small subset of Java developers who care about such things.
🔥Flame On! 🔥
Parlog is drawing a distinction that may be true, but both confuses regular Java developers and actively discourages all developers from using Java.
First, a bit of background. Starting with Java 10 (March 2018), Oracle began releasing major versions of Java every six months. Up until then, a delay in a particular feature could delay the overall release of Java indefinitely (the dreaded Java Platform Module System is the classic example, which delayed Java 9 for nearly 18 months, but don’t get me started on JPMS). Oracle didn’t want that to happen any more. They decided it was better to maintain a fixed release schedule and slip features that weren’t ready into the next release, rather than delay an overall language release like that again.
Ever since, Java has had a major release every March and every September. The result is that the current version of Java is 24, which came out in March 2025. The next one will be Java 25, scheduled for September 2025. Some of those releases have been pretty minor. If you follow the OpenJDK release page, you’ll see an index of each version and which JEPs (Java Enhancement Proposals) were included in it. If you look at Java 20, for example, you’ll see that every single included feature was either “incubating” or a “preview”, and if you tend to avoid features like that, the release had nothing in it at all. Other releases, however, like 21, had lots of new capabilities.
The thing is, most companies are not interested in updating their production versions of Java every six months, and that’s what Oracle was recommending. Updating every Java server in an organization can be expensive, especially if they all need to pass security audits and licensing issues. Oracle basically claimed that each version of Java was only going to be supported for six months. That meant that once Java 21 came out, as far as Oracle was concerned, Java 20 was finished.
(It’s actually worse than that. Oracle releases a small update every two months, so Java 25 will be followed by 25.0.1 in two months and 25.0.2 two months after that, followed by Java 26 next March and we start the cycle again.)
Yeah, that wasn’t going to work, so when coming up with the six-month cadence, they also decided to establish what they called Long Term Support (LTS) versions. For an LTS version, Oracle guaranteed security patches and bug fixes for a minimum of three years, which was later shortened to two years. They retroactively designated Java 8 as an LTS version, then the next few were Java 11, Java 17 (three years later), and Java 21 (two years after that). By that measure, the next LTS version of Java will be Java 25 two years after Java 21.
The unsurprising result was that the usage of “non-LTS” versions dropped like a rock. Whenever a market share study is released, the number of developers using a non-LTS version, like 13, 15, or 19, is virtually nonexistent. This annoys the team at Oracle, of course, because to them every version of Java is significant. I’ve heard the Oracle team suggest that while they understand you might not want to upgrade the production versions, individual developers can certainly try out the latest version, whatever it is, and use the new features. They say that with a straight face, too, as though any developer in a company is going spend time working with features they’re not allowed to put into production. I’m sure it happens, but not often and not a lot.
Getting to the heart of the issue, Parlog’s entire video argues that while the LTS designation may be a useful, it’s not actually correct to describe a Java release that way (geez, I can almost hear the “well, actually” part in my head). See, Java is actually a specification. The JDK itself is actually a binary product of a build of the open source code. Neither is actually associated with the term LTS. That’s reserved for Oracle’s support contract definitions, and other vendors are free to produce their own binaries and their own support offerings however they want to. When you refer to LTS, you’re actually talking about a particular maintenance product from Oracle, and that’s all. Java 25 isn’t an LTS release. The Oracle’s JDK 25 binary is actually the LTS version.
Yeah, right. Unless you work for Oracle, or are a lawyer, or are (worst of all) an Oracle lawyer, NOBODY CARES about that distinction. What developers and companies want is to know how stable the product they’re using is, and a stable product for more than six months is better than less.
Also, the claim that other vendors can all do their own thing is specious at best. Oracle is the 800-pound gorilla in this space. Every other vendor (Azul with Zulu, Amazon with Coretto, IBM with Semeru, Adoptium with Eclipse Temurin, etc) is going to adopt the same cadence, or offer something very similar. Sure, they may tinker with the details and they may decide to take pity on their customers that won’t upgrade and continue to offer support beyond six months, but that’s what competition is all about, and for Oracle to describe that as “harm” is a joke.
Essential vs Accidental Complexity
Here’s the real key for me: there’s a difference between essential complexity and accidental complexity. In 1986, the computer scientist Fred Brooks published a seminal article on software entitled, No Silver Bullet, where he discussed the difference between essential complexity, which is part of the system that is inherently complicated and can’t be eliminated, and accidental complexity, which grows up around the code and can sometimes be reduced, by (for example) adopting a better IDE or a simpler process. Heck, where AI tools are changing my life these days is by reducing accidentally complexity by huge amounts.
I like to think about concurrent programming as an example. These days even my phone has multiple cores on it, so we would like to run as many processes in parallel as possible to take advantage of that power. Tons of work has been done on concurrent coding in Java and related languages, ranging from virtual threads in Java 21 to coroutines and structured concurrency in Kotlin to reactive streams in Java and Javascript, and more. Each approach makes writing the actual concurrent code much simpler. But once you start writing code like that, you quickly discover that understanding asynchronous concepts is still hard, and even though the code may be straightforward, the concepts are tough and debugging concurrent activities is really difficult.
Put a pin in that. I’ll come back to it.
Java Champions Day
For several years, Oracle liked to hold a Java Champions Day before the annual JavaOne conference. Oracle representatives would be there to talk about upcoming products and releases and to answer any questions we might have. I attended the ones in 2017 and 2018. What I most clearly remember from those Java Champions Days was that we wasted at least half of it each time on the same argument. I’m paraphrasing, but this is how I remember it:
Java Champions: You’re making all of this licensing and release stuff way too complicated, and it’s driving developers away from the community.
Oracle reps: We’re sorry, but all these different rules and conditions are involved. You really need to have your legal team look over the license agreements.
Java Champions: See, that’s exactly the problem! People want a simple, straightforward answer, not “go talk to your lawyers”.
Oracle reps: We’re sorry, but that’s the only responsible answer we can give.
Ugh and yikes. All I could think of was:
Q: How many Oracle representatives does it take to change a light bulb?
A: Check with your legal team, but now that we know you exist we’re going to do a massive audit and send you a huge bill afterwards based on your total number of employees, regardless of whether they’re even aware of lightbulbs.
Look, I get it. Some companies need fully-supported, licensed versions of Java, both for legal indemnification and actual support. I seriously doubt that’s more than 10% of the industry, and I expect it’s much, much less. I also understand that developing and supporting Java costs money. But that’s Oracle’s problem, not mine, and I really don’t appreciate Oracle’s attempt to push such accidental complexity onto developers, claiming it’s essential complexity when it’s not. Heck, for the first decade of Java’s existence, all of this was trivial and almost nobody paid anything.
Some people don’t understand that being called “hopelessly pedantic” is not a compliment, and apparently a lot of those people work for Oracle. Parlog (who of course works for Oracle) argues quite forcefully that it is mandatory that everyone understand the details of the definitions of all the LTS-related terms and how they’re implemented. Then you’ll have to do it all over again when Oracle changes its business model yet again (as they are free to do at any time) to squeeze more money out of the industry.
Here’s my simplified answer. Yes, by all that is holy, Java 25 most definitely IS an LTS version, just as Java versions 8, 11, 17, and 21 were before it. If you need specifics, the details are available from your JDK vendor, but don’t waste your time or energy on that unless you absolutely have to. That way lies madness, and that’s what lawyers are for. I just wish Nicolai on the official Java YouTube channel (current subscriber count: about 237K) wouldn’t keep insisting that Java developers are wrong when they think about LTS, especially when abstraction is at the heart of what we do.
(Note for potential commenters: Feel free to disagree with me, but if I’m reading your comment and can hear the “well, actually” in my head, it’s going to be really hard for me to take you seriously. Still, jarheads are the awesomest of the awesome, so I’ll listen to whatever you have to say.)
🧯Flame Off 🚒
We now return you to our regularly scheduled newsletter, already in progress.
Scary Awesome AI
This week I taught my first Claude Code training course on the O’Reilly Learning Platform, and I feel it both went well and went quite differently from what I was expecting. All my preparation resulted in a set of slides I was happy with, along with several sample apps that I planned to use in the course. As it happened, we only used two or three of them, but we still covered the material I wanted to cover.
Of all the AI agent tools, Claude Code is still my favorite. I can rely on it to understand what I want and at least try to supply it.
Later in the week I was doing a long-overdue updating my regular Spring and Spring Boot materials, and ran out of tokens for a time. Claude Code throttles your usage with limits that renew every five hours. The problem is that as a project grows, it uses up more and more tokens to do relatively simple things, so at about 3 pm one day it told me to take a break until 4.
I tried switching to OpenAI’s Codex CLI, only to discover that they’d changed everything since I last used it. There’s no upgrade path, either: you have to uninstall the original one (a node-based app) and reinstall using Homebrew on a Mac, because the new one is apparently written in Rust.
Okay, so be it. But when I tried to run it, I got login problems, and when I got past that and tried to use an MCP service inside, it kept timing out when I tried to connect and there was no way to resolve that.
I therefore decided it was time to give Google’s Gemini CLI a chance. That worked, but kept failing to give me what I wanted. I tried to give it an example in another folder. First I asked it if it could see the file, and it said yes. Then I asked it to use that file as a sample, and it complained that because it was outside the current folder, it couldn’t read it. Sigh. So I made a copy and put it in the same directory and tried again, and instead of giving me something similar, it generated a minimal implementation that didn’t do anything I wanted. A bit of arguing eventually got something remotely close, but by then Claude Code was back and I could become productive again. After I was done, I told Gemini CLI to look at the changes, and it complimented them a lot. I told it that’s what I want in the future and told it to save those instructions somewhere, but we’ll see if that works.
I think I mentioned Playwright last week, but as a quick summary it’s a browser automation tool that executes end-to-end tests. It can access a website, fill in a form, click a button, take a snapshot of the current window, and verify that what you expected to happen did happen.
I was trying to figure out how to demonstrate that, so of course I asked Claude. It told me about three sample e-commerce apps available on the web:
Playwright has an MCP service, so I configured that for Claude Code, started it up in an empty folder, and told it to use Playwright to generate a reasonable set of tests for that site. Keep in mind I have no idea how those sites are implemented, and I only know enough about Playwright to be dangerous.
The next thing I knew it had a nice set of tests. I loaded the project into WebStorm (from JetBrains) and it gave some warnings. I gave those back to Claude Code and it quickly fixed them.
The result was a set of tests that ran successfully, in parallel, on three different desktop browsers (Chrome, Firefox, and Webkit) and two mobile browsers (Android and iOS).
Wow. That falls into the category I now call scary awesome — it’s amazing to be that productive that quickly, but it’s scary to think how expendable I am (or might become) in that scenario.
I think I’ll pick one of the other sample sites and record a video showing the same process. If I get to that soon, I’ll let you know. I have a conference coming up in a bit over a week (UberConf in the Denver area) so I’m pretty busy, but we’ll see.
Social Media
July 4th
I think that’s about as political as I want to get this week.
Sounds about right
That analogy is a bit too on the nose.
Stripes
Hey, horizontal stripes are supposed to be slimming.
Explanation from last week
I had multiple people say they didn’t get this, so here are your clues:
The runner’s name is pronounced “who”.
That’s first base.
Hey, any excuse to watch that routine again is a good one.
It’s all true
English is weird.
Video or it didn’t happen
Pretty dated joke (rimshot), but I laughed.
Fun with photo rendering
I went to ChatGPT and uploaded this photo:
I told it to render me in the style of Studio Ghibli. Here’s the result:
That’s good, but honestly it makes me think I’m wandering the streets of Anatevka trying to muster up the courage to ask Tevya for the hand of one of his daughters.
This is me in a George Booth cartoon:
Finally, I asked it to put me in a Tales from the Far Side cartoon, given that this newsletter is called Tales from the jar side:
I don’t know what the jars are saying, and I don’t think I want to know.
Let’s end with a bad pun:
But it’s not
Have a great week, everybody. :)
Last week:
Claude Code, on the O’Reilly Learning Platform
This week:
No classes, but an interesting day trip lined up that I’ll tell you about next week.
I think what's confusing to many, yourself included it seems, is not so much LTS but rather the way versions are named. So let's ignore version names for a moment, and see what's changed.
For many years, at least since JDK 7, every 6 months there was a feature release (back then they were called "limited update" releases) containing large new features and significant changes to the runtime. Java users had no choice but to upgrade to those new feature releases because the old feature releases were abandoned and received no security patches. Because these feature releases were occasionally disruptive, this caused problems to those people who just wanted to get security patches.
So change number one was the introduction of LTS, which, for the first time allowed Java users who wanted just security patches to stay on old feature versions and just get patches without being forced to upgrade to a new feature release every six months.
The other problem was that while feature releases could and often did make drastic changes to the runtime, they could not make any additions to the spec. That means that overhauling the JIT compiler or the GC algorithms (or adding a whole new UI toolkit as long as it wasn't made part of the SE spec) were done in those feature releases, they couldn't so much as add a new method to the ArrayList class, as that meant changing the spec. Changes to the spec were only allowed in "major releases", which, as you correctly note, were irregular and far between.
So change number two was doing away with major releases altogether and removing the restriction that the semi-annual feature releases could not change the spec. This meant that, in addition to 50KLOC of internal changes, feature releases could now a method to ArrayList.[1]
Yet another problem -- the most superficial one -- was how versions were named. 7u1 and 7u3 were patch updates, but 7u2 and 7u4 were big feature releases (and again, people had no choice but to upgrade their JDK from 7u2 to 7u4 because the feature version 7u2 stopped getting patches once the new feature version, 7u4, came out). To make matters worse, the version naming scheme was changed in 8, where the big feature releases were named 8u20 and 8u40 (again, 8u20 was abandoned when 8u40 was released), while patch releases were named something like 8u25.
So change number 3 was to make the version names more meaningful, and with major releases gone this was easy: feature releases would get integer names, while patches would get dot names. The version that would have been named 9u20 was, instead, named 10. This most superficial change that was meant to make it easier for people to understand what's included in the release, unfortunately confused people the most. Some companies stopped making the very same upgrades they'd made for many years (because they had no choice) and relied on the LTS service for patches even when it would have been easier, cheaper, safer, and more productive for them to continue upgrading feature releases as they'd done before.
[1]: But did this make the feature release upgrades harder than before? Maybe a little, but not for long. After JDK internals were encapsulated in JDK 16, those who regularly update feature releases report the process easier than for the feature releases in the JDK 8u and 7u era. This isn't so much because feature updates are perfect, but largely because they were particularly problematic in the past, which is why introducing LTS so that, for the first time ever, those who really don't want or need new features or performance enhancements wouldn't have to upgrade.
Regarding LTS, well put! I totally agree with your logic. We are developers, not lawyers. I'm pretty sure we won't reach java versions 50, 75 or 100, because I have a feeling they'll change release cadence and/or versioning in a few years.
P.S. Still, Java is a great language and I wouldn't want to ditch it.