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
>> 
>> 
>> 
>> 
>> 
>> 

Reply via email to