Just pushed an updated UPGRADE.md - https://github.com/apache/cayenne/blob/STABLE-5.0/UPGRADE.md
It collapses milestones upgrade sections into a single "story": how to go from 4.2 to 5.0. On top of it, I literally asked Claude - look at John's message and make sure all the questions are answered :) Which it did. Still, the file is huge (762 lines!), so in practice it is only suited for LLM consumption. I am thinking of maybe creating a "cayenne-5-upgrade" AI skill (https://github.com/apache/cayenne/tree/STABLE-5.0/ai-plugin) on top of it. Or maybe I am overcomplicating the problem and the skill is literally: "Run the XML upgrades and then take a diff between STABLE-4.2 and STABLE-5.0 branches and figure out what needs to be done to my project". Andrus > On Nov 3, 2026, at 10:55 AM, John Huss <[email protected]> wrote: > > Thanks for asking! I don't want to bellyache - I get paid to do this stuff, > so these are not terrible problems. But users should expect this upgrade to > require some real investment from them - it's not a drop-in at all, > especially due to the DataSource/Node changes which will hit everyone. A > lot of the rest depends on how deeply you customize the stack. Since you > asked I'll list the things I encountered. I'm not saying these are bad > changes, just that this is certainly a major release. I can imagine some > users beginning to upgrade and then stopping because of all the problems. > It's not a slam-dunk. I'm basically the sole maintainer of 7 different > server applications that all use a single shared version of cayenne, so > when I upgrade I have to upgrade them all. > > Things moved to different packages - IDE can find all these if the package > was the only change; but often the class name changed too. > Replacing ServerRuntime with CayenneRuntime - not hard, but of little value > to users > Replace BaseContext with DataContext - not hard, but of little value to > users > Rename ServerRuntime.builder to CayenneRuntime.of - not hard, but of little > value to users > Renaming of all AST* classes (not automatic) - The new names are definitely > better, but this was annoying for little value returned > Replace PersistentObjectList with ToManyList - not hard, but of little > value to users > > Entity object hierarchy - replace BaseDataObject with PersistentObject, > etc. Changes break Jackson JSON mixins due to new hierarchy. > My custom DataDomain subclass wasn't important enough to keep due to API > changes (not automatic) > API changes to DefaultSelectTranslator (not automatic) > Custom ObjectContext subclass was difficult to install now - there are not > great extension points for this use case. I had to modify the cayenne > source to make this easier to extend without a ton of duplication. > Custom JdbcAdapter - changes to Constant names in the constructor (not > automatic), API changes like using NativeColumnType. > Removal of DbAdapterFactory requires new way to install custom DbAdapter > and PkGenerator > Removal of DataNode and DataSourceFactory changes the way a DB connection > is established - lots of work needed to runtime creation and > binding/injection - removed extension points. Since the data source was set > up using properties (strings) and it was harder to verify everything was > correct. I've not been a fan of the binding/injection because it isn't > discoverable. As a user I have seemingly no way of knowing what extension > points are available and how to use them. The pro-tip is to read the source > for CoreModule.java but that's not a great answer, and I don't know if that > is even exhaustive. > Removal of thread bound runtime without a built-in replacement. This is > probably the hardest to address for users. I ended up just re-introducing > the same concept almost exactly. In Java 25, this can be better with > ScopedValue, but sadly I'm not on 25 yet. Thread-bound object context > remains, but is deprecated - I'll wait and do that one later I guess. > > The silver lining is that LLMs help a lot with this. You can provide them > with the cayenne source code to refer to and they can rock a lot of this > stuff. And the carrot/prize is that you get much improved performance! I'm > still +1. > > > > On Mon, Nov 2, 2026 at 4:28 PM Andrus Adamchik <[email protected]> wrote: > >> >>> On Nov 2, 2026, at 3:32 PM, John Huss <[email protected]> wrote: >>> >>> I will say that upgrading to this release requires a LOT of effort. There >>> are SO many breaking changes. I get that this is a major version update, >>> and I am totally thrilled with the performance improvements. But golly, >>> this is a huge effort. It is very fortunate that we have LLMs now to help >>> with the transition, but this is still a rough process. I hope the amount >>> of breaking changes is going to slow down. >> >> Hi John, >> >> Thanks for sharing your experience. This is important, as soon others will >> start upgrading. Before I go on a rant, a question :) Can you describe your >> upgrade experience? What you needed to change and what was the hardest to >> deal with? What else can we add to UPGRADE.md to make things easier? >> >> >> Now my thoughts on the rate of change... Years ago we've noticed how >> adding even a smallest new feature is insanely hard due to how many edge >> cases needed to be handled. This is why we still don't have horizontal >> inheritance. The amount of tech debt that accumulated over two decades is >> staggering. So the product stagnated and got confusing. Just some examples: >> >> 1. Depending on how you count, there are between 15 and 23 Query >> subclasses in 4.2! How do you explain this to users? And how do you support >> all of those runtime paths? >> 2. User-facing APIs from different moments in history (DataContext has >> older methods that are not in ObjectContext; query methods are different >> between fluent queries and the context) >> 3. Three different event models (callbacks, snapshot events, filters) >> 4. Four independent cache subsystems >> >> LLMs presented a unique chance to finally get unstuck, drive towards a >> consistent mental model that can be communicated to users and meaningfully >> improve the product. But it did (does) require what essentially amounts to >> an ongoing open-heart surgery that we never dared to do before. >> >> I understand that 90% of our users have extensive existing code bases, so >> we are creating extra pain. While the core behavior is unchanged (I am >> trying to keep all the faulting contracts and such) and the model upgrade >> is done automatically, there are dozens of API changes across the board. >> More so if people built extensions based on (formally public, but outside >> ObjectContext / Query) APIs. Those things will be broken and will need to >> be redesigned as they cannot be preserved in a meaningful way. >> >> So I am constantly asking myself if we got carried away too much and when >> we should stop. From what I see now, at least 6.0 is going to be another >> breaking release (BTW it is down to just 4 essential Queries already :)). >> Hopefully we deal with all mapping and Query restructuring there and later >> work on things like caches quietly without user disruption. >> >> Andrus >> >> >> >> >> >>
