The Invisible Contract Between a Framework and Its Users — And What Happens When a Major Version Breaks It
When a team adopts Spring, Hibernate, or any long-lived Java framework, they are not just choosing a library. They are accepting a set of implicit promises about the future — promises that the framework’s authors have never written down, never signed, and bear almost no cost to break.
The Contract No One Signed
Somewhere in the first week of a new project, a team chooses its stack. They reach for Spring Boot because their last three projects used it. They pull in Hibernate because it ships with the Spring Boot BOM and they already know it. They are not making a one-time technical decision. They are, whether they know it or not, entering into a long-term relationship with the teams who maintain those frameworks — a relationship governed by implicit expectations, not explicit agreements.
Those expectations are not unreasonable. When you adopt a framework that has existed for ten or fifteen years, written thousands of lines of idiomatic code around it, trained your team on its conventions, built your test infrastructure around its abstractions, and anchored your mental model of how applications are structured to its particular way of seeing the world — you have done so on the assumption that the framework will continue to make sense of that investment. That the API you learned will still exist. That the HQL you wrote will still parse. That the security configuration you spent a week getting right will not need to be rebuilt from scratch when you take the next security patch.
That assumption is the invisible contract. And it is invisible precisely because no one in the open-source ecosystem has any incentive to make it explicit. If you wrote it down honestly, it would look uncomfortable.
A team adopts a framework. They are not choosing a library — they are accepting a set of promises about the future that no one has written down, and that the framework’s authors bear almost no cost to break.
The contract is not legally enforceable. It is not written in any README. The MIT license, which governs most of the frameworks under discussion, explicitly disclaims any warranty whatsoever. And yet the relationship functions as if a contract exists, because both sides behave as though it does. Framework authors write migration guides and deprecation notices. They maintain two major versions in parallel during transitions. They apologize in blog posts for the difficulty of certain upgrades. These are not the behaviors of parties who believe they owe nothing. They are the behaviors of parties who know, at some level, that they do.
The Asymmetry Problem
The deepest structural issue with the invisible contract is not that it exists. It is that it is radically, almost comically asymmetric. Consider the actual distribution of migration costs when a major version break occurs.
The framework authors write a migration guide. They run their own test suite against the new version. They perhaps spend a few weeks helping key community members and commercial partners through the transition. The effort is real, but it is bounded and largely focused on the future: designing the new API, writing the new documentation, building toward whatever architectural goal the major version was meant to achieve. The past — all the production code already written against the old API — is not their problem in any immediate, concrete sense.
The users, by contrast, own every line of that migration. They must read the guide, assess the impact on their specific codebase, update dependencies, refactor thousands of import statements, rewrite security configurations, debug subtle behavioral changes in query results that only manifest at runtime, update every library in their transitive dependency tree that is caught in the crossfire, and then perform all of this work under the same delivery pressure that applies to features their actual customers care about. The cost is borne entirely by the downstream party. And crucially, it scales with adoption — the more successful the framework, the more total migration cost its major version breaks impose on the ecosystem.
Where migration cost actually lands: framework author vs. user

This asymmetry is not unique to software. It appears in many long-term supplier relationships: the party that controls the specification bears less of the transition cost than the party that has built operations around it. What makes the Java framework situation unusual is the scale of the investment users have made, and the degree to which that investment was made in good faith based on signals the framework authors themselves deliberately sent.
When the Spring team markets Spring as enterprise-grade, production-ready software for building systems that will run for years, they are not just describing capabilities — they are building expectations about stability. When Hibernate publishes detailed O/R mapping guides and encourages teams to write complex HQL queries tuned to specific version behaviors, it is implicitly suggesting that those queries have a future. The investment in deep framework knowledge is not accidental. It is the direct consequence of the framework authors wanting teams to invest in deep framework knowledge, because adoption depth is what creates the ecosystem flywheel that sustains the project. Then the major version arrives, and the depth of that investment is precisely what makes the migration expensive.
Three Breaks Examined — Spring, Hibernate, Jakarta
Case Study 1
Spring Boot 2 → 3: The Namespace Tax and the Cascading Bill
Spring Boot 3’s requirement for Java 17 and its adoption of the Jakarta EE namespace were architecturally coherent decisions. Jakarta EE’s renaming of javax.* to jakarta.* was a real external constraint, not an arbitrary choice by the Spring team. Java 17’s improvements in performance and security were genuine reasons to raise the baseline. Taken individually, each decision made sense.
Taken together, they created a migration event that cascaded through entire dependency trees. Migrating to Spring Boot 3 did not simply mean changing import statements — though it meant that too, potentially thousands of them across large codebases. It meant simultaneously upgrading Spring Security (which replaced WebSecurityConfigurerAdapter with SecurityFilterChain, requiring a complete rewrite of security configuration patterns), waiting for every third-party library in the dependency graph to publish Jakarta-compatible releases, and coordinating all of this while the application still needed to ship features. For organizations where migration was not yet feasible, third-party vendors stepped in to offer paid long-term support for the abandoned version — a market that only exists because the transition cost was high enough to make paying for it rational.
JDK 11 → 17 mandatoryjavax.* → jakarta.* across all importsSecurityFilterChain rewrite requiredThird-party ecosystem lag of 6–18 months
Case Study 2
Hibernate 5 → 6: When the Query Language You Relied On Changed Silently
The Hibernate 6 migration is a particularly instructive case because a significant portion of its breaking changes were behavioral rather than syntactic. The implicit SELECT clause behavior changed in a way that does not cause compilation errors — it only causes bugs at runtime. In Hibernate 5, a join query that selected from multiple entities returned results containing all joined entities. In Hibernate 6, the same query returns only the root entity. The code compiles. The tests might even pass, if they are not asserting on the structure of the result set. The bug only surfaces in production when data that was previously returned is suddenly absent.
Beyond that, HQL syntax that had been accepted for years was removed: column names in place of attribute names, the optional FROM keyword in update statements, collection pseudo-attributes like .size and .elements. These were not obscure features. They were patterns found throughout real production codebases precisely because Hibernate’s own documentation and tutorials used them. The investment users made in Hibernate-specific HQL patterns was actively encouraged by the framework’s own communication. The migration guide was thorough. But thoroughness in a migration guide does not reduce the work of actually executing it — it only makes the work more traceable.
Runtime behavioral breaks (not compile-time)HQL syntax removals across entire codebasesLegacy Criteria API fully removedQuery result type changes (BigInteger → Long)
Case Study 3
The Jakarta Namespace Migration: A Break Imposed by IP Law, Paid for by Users
The javax.* to jakarta.* namespace change occupies a special position in this discussion because it was explicitly not a technical decision. It was a legal one, forced by Oracle’s retention of the javax trademark when it transferred Java EE to the Eclipse Foundation. The Eclipse Foundation could not evolve the APIs under a name they did not own. So the namespace changed.
What is striking about the Jakarta migration is that nobody chose it, nobody wanted it, and the entire cost was paid by the people with the least influence over the decision: the developers maintaining enterprise Java applications. Oracle bore none of the transition cost. The Eclipse Foundation bore the administrative cost of managing the rename. The framework maintainers — Spring, Quarkus, GlassFish, Tomcat — bore the cost of supporting two namespaces in parallel for a transition period. The developers writing enterprise Java applications bore everything else: auditing codebases, updating import statements, resolving dependency conflicts between libraries that had migrated and those that had not, and debugging the peculiar runtime errors that arise when javax and jakarta classes mix on the same classpath.
Root cause: Oracle trademark restrictionjavax.* and jakarta.* incompatible at runtimeDependency conflicts from partial ecosystem migrationTooling (OpenRewrite, Eclipse Transformer) partially automated rename
Why Open-Source Culture Systematically Undervalues Existing Users
The asymmetry of migration cost is not a product of bad intentions. It is a structural consequence of how open-source projects work, what they reward, and whose interests are most visible in the community that surrounds them.
Open-source contributors are, by definition, people who are actively engaged with the codebase. They file issues, submit pull requests, participate in design discussions, and show up at conferences. The community that is visible to framework maintainers is the community of active contributors and early adopters — people who are excited about what comes next, who want to explore new APIs, who have opinions about architecture and are not afraid to share them. These are the people whose interests frame the conversation about what a major version should contain.
The people who bear the most migration cost are, by contrast, largely invisible. They are the developers at banks, insurance companies, government agencies, and healthcare systems who are running Spring Boot 2.7 in production because the risk of migration outweighs the benefit for their specific context. They are not filing issues about the new API because they have no opinion about the new API — they are trying to keep the existing one running safely. They do not show up in community discussions about breaking changes because they have no voice in those discussions. Their representative in the process, to the extent they have one, is the migration guide — a document that describes the cost of a decision they had no part in making.
The contributors most visible to framework maintainers are excited about what comes next. The users who bear the most migration cost are busy keeping the current version working — and they are largely invisible.
This dynamic is compounded by the way open-source sustainability is increasingly structured. Publishing a library with a nice README and detailed installation instructions creates implicit expectations between maintainer and user — a point that open-source advocates have debated at length. But the debate has mostly focused on whether maintainers owe users anything. The more interesting question, for frameworks at the scale of Spring or Hibernate, is what kind of obligations are created when a framework explicitly positions itself as infrastructure for enterprise production systems, actively encourages teams to build deep, idiomatic knowledge of its specific behaviors, and then releases a major version that renders that knowledge partially obsolete.
Furthermore, there is a subtler incentive structure at work. Major version releases are the most publicly visible moment in a framework’s lifecycle. They generate blog posts, conference talks, YouTube tutorials, and JCG articles. They attract new users. The excitement around Spring Boot 3’s GraalVM native image support and virtual thread integration was genuine and deserved — these were significant architectural advances. But that excitement is concentrated among people who are not currently running Spring Boot 2 in production. The people who are running it in production experience the major release primarily as a cost, not a benefit, at least in the short term. Open-source culture rewards the creation of things. It has no particularly good mechanism for rewarding the careful management of the cost of change.
| Stakeholder | Visibility in framework community | Migration cost borne | Voice in breaking-change decisions |
|---|---|---|---|
| Framework maintainers | Maximum | Minimal (guide writing, tooling) | Complete |
| Active community contributors | High | Low (engaged early, on latest) | Significant |
| Greenfield commercial users | Medium | None (start on the new version) | Moderate |
| Enterprise production users (legacy) | Low to none | Very high | Near zero |
| Regulated-industry operators | Essentially zero | Highest (compliance, audit trails) | Zero |
What an Honest Framework Contract Would Actually Say
Suppose a framework team sat down to write the actual terms of the relationship they are entering into with their users. Not the MIT license boilerplate, not the marketing copy — the real expectations, in plain language. An honest version might look something like this:
Draft — The Framework User Agreement (Unwritten Edition)
- We will signal breaking changes clearly via major version increments and publish migration guides before the release, not after. We commit to at least one major version of parallel support during transitions of significant scope.
- We will distinguish between public API and implementation and we will only break the public API when the architectural gain is proportionate to the migration cost we are imposing. We will not break public API to clean up code we find aesthetically unpleasant.
- We acknowledge that behavioral changes without compile-time errors are the most dangerous category of break and we will treat them with higher scrutiny than signature changes, not lower.
- We acknowledge that the migration cost scales with adoption. When our framework is used by a significant fraction of the Java ecosystem, a major break is not merely a software event — it is an economic event with measurable cost to the organizations and individuals who trusted us with their infrastructure decisions.
- We will provide automated migration tooling for any break that can be mechanically expressed. If we cannot express a migration mechanically, we will consider whether the break is justified at all.
- We will not end-of-life a major version until at least N years after its initial release, where N is disclosed at release time and not changed retroactively. We will continue providing security patches on EOL versions for at least M additional months.
- We accept that our users cannot always migrate on our timeline and we will not use security vulnerability pressure as the primary lever to force migration when the underlying cause of the vulnerability is architectural rather than a patch-able defect.
None of these clauses is unreasonable. Several of them are already partially honoured by the better-stewarded frameworks in the Java ecosystem. But none of them is written down anywhere. They exist only as informal norms, enforced entirely by social pressure and the threat of users eventually choosing alternatives — incentives that are too diffuse and too slow to protect the developers currently staring at a migration guide wondering how to justify three weeks of refactoring to a product manager who wants to know why the feature backlog is stalled.
The Java platform itself represents the gold standard for this kind of explicit commitment. Bytecode compiled against Java 1.0 still runs on Java 21 — a roughly thirty-year compatibility window maintained across 25 major versions. That commitment is not automatic or costless; it requires the JDK team to carry forward decisions they might prefer to abandon. But it is what makes Java a viable foundation for systems that must operate for decades. The framework layer inherits the credibility of that commitment. It is worth asking whether it honours the spirit of it.
The Obligations Framework Authors Rarely Acknowledge
There is a version of the maintainer perspective that is entirely defensible. Open-source frameworks are built and maintained by people who are, in the most direct sense, giving their work away. The MIT license is not a rhetorical device — it is a literal description of the terms. And the argument that maintainers owe users nothing, that the expectation of ongoing stewardship is an entitlement that the community has constructed without the maintainers’ consent, has a genuine philosophical basis.
But this argument, though defensible for a small library written and maintained by a single developer, becomes increasingly strained as a framework grows in adoption, institutional endorsement, and commercial backing. Spring is not a hobbyist project. It is backed by Broadcom, was previously owned by Pivotal and VMware, and represents the primary framework choice for a substantial fraction of all Java application development globally. Hibernate is the reference implementation of a Java specification. These projects exist in a fundamentally different category from weekend-project open-source software, and the obligations they carry are correspondingly different.
When a framework positions itself as enterprise infrastructure — when it publishes case studies from Fortune 500 companies, partners with cloud vendors, offers commercial support contracts, and advertises itself at the enterprise architect level — it is claiming a place in an implicit chain of responsibility. Enterprises do not adopt Spring Boot because it is free. They adopt it because it presents itself as reliable, stable, well-governed infrastructure that can be trusted as the foundation of systems that must operate without disruption for many years. That positioning creates expectations that the MIT license cannot fully disclaim.
The commercial reality matters here. When HeroDevs can build a sustainable business offering paid long-term support for Spring 5.3 after its EOL, that market exists only because the migration cost to Spring 6 was high enough to make paying for security patches on the old version rational. The framework’s commercial backers captured the value of the transition. The users paid the cost of it, twice — once in migration effort, once in support fees.
There is also the question of what specifically constitutes an obligation in this context. The most defensible obligations are not the most demanding ones — they are the ones that require good-faith acknowledgment rather than heroic effort. They include being honest about the migration cost in communications, not just the benefits of the new version. They include providing migration tooling wherever mechanically possible. They include treating behavioral breaks — changes that do not compile but change runtime results — with the same gravity as API breaks, because they are often more dangerous. And they include acknowledging, explicitly, that the users with the least capacity to migrate quickly are often the ones operating the most critical systems: healthcare, financial infrastructure, government services.
None of this requires a framework to stop evolving. Stagnation is its own kind of failure of stewardship. What it requires is a more honest accounting of the cost side of the ledger — a recognition that every migration burden imposed on the ecosystem is a real tax on real teams, and that the magnitude of that tax scales with exactly the thing that made the framework successful in the first place: the depth and scale of its adoption.
The more successful a framework becomes, the more users depend on its stability, and therefore the more disruptive any major change becomes. Success makes breaking changes more expensive, not less. A framework that treats its adoption depth as evidence that users will absorb migration costs regardless has inverted the relationship between success and obligation.
What better looks like in practice
To be fair, the Java framework ecosystem is not uniformly indifferent to these concerns. Spring’s dual-version maintenance windows, Hibernate’s attempt to provide compatibility shims, and the OpenRewrite project’s efforts to automate migration at scale are all evidence of maintainers taking migration cost seriously in practice, even if they rarely frame it in the language of obligation. The OpenRewrite recipes for the Spring Boot 3 migration, in particular, represent a genuine attempt to make the cost of a major break mechanically tractable — and that effort deserves acknowledgment.
What is missing is the explicit, upfront framing. The migration guide that says: we estimate this migration will take a team of four between three and six weeks on a mid-size codebase, and the behavioral changes in sections 4 and 7 require manual verification that no automated tool can provide. The release announcement that leads with the cost before the features. The public commitment to a minimum support window that is made at the time of release, not discovered in the EOL announcement. These are small changes in communication practice that would go a long way toward making the implicit contract honest.
Because the alternative — continuing to treat the migration burden as the users’ problem alone while the framework’s authors capture the credibility of enterprise adoption — is a kind of bad faith that, over time, the Java ecosystem as a whole pays for. Teams that have been burned by expensive migrations become more conservative about adopting frameworks, more reluctant to take on dependencies, more likely to write their own infrastructure code that they can control. That conservatism has costs too. The invisible contract, when it breaks, damages more than the specific migration it creates. It damages the trust that makes large-scale open-source infrastructure adoption possible at all.
What We Have Learned
The relationship between a Java framework and its users is not, at its core, a technical relationship. It is a social one, governed by implicit expectations that neither side has written down. Those expectations include stability commitments, communication norms about breaking changes, and an understanding that the investment users make in deep framework knowledge was made in good faith on signals the framework authors deliberately sent.
Major version breaks — Spring Boot 2 to 3, Hibernate 5 to 6, the Jakarta namespace migration — reveal how asymmetric that contract is. The framework authors bear the cost of designing the new system and writing the migration guide. The users bear the cost of executing the migration, which scales with exactly the depth of adoption that made the framework successful. The people with the most at stake — enterprise teams maintaining critical production systems — have the least voice in the decisions that impose that cost.
Open-source culture compounds this problem by making active contributors visible and production users invisible, by rewarding creation and not the careful management of change, and by allowing a legal disclaimer to stand in for genuine acknowledgment of the obligations that enterprise positioning creates.
What honest stewardship would look like is not complicated: upfront cost estimates in migration guides, explicit support window commitments at release time, automated tooling for mechanically expressible breaks, and genuine acknowledgment that behavioral changes without compile-time errors are more dangerous than API renames. None of this requires stagnation. It requires honesty — about what the framework has taken from its users in the form of trust and adoption, and what it therefore owes them in the form of transparency about the cost of change.



