Under semantic versioning, a breaking change is any backward incompatible change to the public API, and it requires a major version bump. The catch is that the public API is whatever the author declares it to be. Behavior changes, defaults and security fixes can still break you inside a minor release.
What the specification says
The Semantic Versioning 2.0.0 text says the major version X, for X greater than zero, must be incremented if any backward incompatible changes are introduced to the public API. It may also include minor and patch level changes, and patch and minor are reset to zero. Minor versions add functionality in a backward compatible manner, and patch versions are for backward compatible bug fixes.
The spec also depends on an author declaring a public API. The promise only covers what has been declared.
Where reality differs
| Change | Declared breaking? | Can still break you |
|---|---|---|
| Removing or renaming a public function | Yes, major | N/A |
| Changing a default value | Depends on the project | Yes |
| Fixing a bug you relied on | Usually patch | Yes |
| Tightening validation for security | Often minor or patch | Yes |
| Dropping an old runtime version | Project decides | Yes |
| Changing error messages or ordering | Rarely | If you parse them |
Security fixes are the interesting row. A fix that rejects malformed input, changes a parser default or refuses an unsafe path is correct and may still break a caller who depended on the loose behavior. A project may ship it in a minor release because the declared API did not change.
Symfony's published backward compatibility promise is a good illustration of how seriously mature projects treat this. It says only major releases may break backward compatibility, minor releases must not break the existing API of that branch, and deprecation messages are provided to help migration across majors. It also admits that almost every change can potentially break an application, because even adding a method can clash with a subclass that defined the same method.
How to test for breakage yourself
- Read the changelog for every version you skip, not only the headline release. Look for words like default, now rejects, no longer and removed.
- Run your tests with deprecation warnings enabled before and after.
- Add tests for behavior you depend on that is not obviously in the public API: parsing, error handling, serialization, ordering.
- Compare outputs on real sample inputs between old and new versions.
- Deploy to a canary or staging environment that gets production shaped traffic.
Which upgrades deserve the most care
Rank by blast radius: libraries in the request path, authentication, serialization, and anything that handles untrusted input. A patch release of a logging helper deserves a quick look. A minor release of an HTTP framework or parser deserves a real test pass.
For these, read the diff of the security relevant files if you can. Semver tells you what the author intended, and tests tell you what happened.
Quick check
Can a minor release break my application?
Yes, if it changes behavior you depended on outside the declared public API, or if the author misjudged compatibility.
Does a major version bump always mean a hard upgrade?
No. It means breaking changes are permitted. Many majors affect only a small part of the API.
Safeguard shows which of your dependency updates carry known vulnerabilities, so you can spend test effort where the security payoff is. Try the free scan at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.