[GH-ISSUE #712] Deleting functionality that was deprecated in a previous minor release #4723

Open
opened 2026-06-13 13:03:35 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @fergushenderson on GitHub (May 26, 2021).
Original GitHub issue: https://github.com/semver/semver/issues/712

My interpretation of Semantic Versioning 2.0 is that a bump to the major release version number is required when deleting functionality that was previously part of the public API, even if that functionality has been deprecated in a previous minor release and so is no longer part of the public API in the latest minor release.

Unfortunately the Semantic Versioning 2.0 spec is NOT crystal clear on whether it is OK to deprecate functionality in a minor release and then delete the previously deprecated functionality in another MINOR release. It says only "Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API." but it is not clear about which version(s) of "the public API" should be considered for this clause; IMHO it should be the public API in all versions with the same major version number, but a possible alternative interpretation is that it should be referring only to the most recent version.

The Semantic Versioning FAQ hints that this is NOT what should happen (it says "Before you completely remove the functionality in a new major release" (emphasis added) https://semver.org/#how-should-i-handle-deprecating-functionality. However, although the FAQ is part of the same document that defines the Semantic Version specification, it is presumably not a normative part of the specification, and that part of the wording doesn't use directives like "SHALL" or "MUST".

Can we clarify the wording to make it clear that a MAJOR release is required in order to delete functionality that was part of the public API in any release with the same MAJOR version number?

E.g. change

"8. Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API. It MAY also include minor and patch level changes. Patch and minor version MUST be reset to 0 when major version is incremented."

to

"8. Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API of any previous release with the same major version. A new major version MAY also include minor and patch level changes. Patch and minor version MUST be reset to 0 when major version is incremented."

(Or alternatively, if the intent is to allow functionality to be deprecated and then deleted in successive minor releases without a new major release, can we clarify the wording to make that clear?)

Originally created by @fergushenderson on GitHub (May 26, 2021). Original GitHub issue: https://github.com/semver/semver/issues/712 My interpretation of Semantic Versioning 2.0 is that a bump to the major release version number is required when deleting functionality that was previously part of the public API, even if that functionality has been deprecated in a previous minor release and so is no longer part of the public API in the latest minor release. Unfortunately the Semantic Versioning 2.0 spec is NOT crystal clear on whether it is OK to deprecate functionality in a minor release and then delete the previously deprecated functionality in another MINOR release. It says only "Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API." but it is not clear about which version(s) of "the public API" should be considered for this clause; IMHO it should be the public API in all versions with the same major version number, but a possible alternative interpretation is that it should be referring only to the most recent version. The Semantic Versioning FAQ hints that this is NOT what should happen (it says "Before you completely remove the functionality in a new *major* release" (emphasis added) <https://semver.org/#how-should-i-handle-deprecating-functionality>. However, although the FAQ is part of the same document that defines the Semantic Version specification, it is presumably not a normative part of the specification, and that part of the wording doesn't use directives like "SHALL" or "MUST". Can we clarify the wording to make it clear that a MAJOR release is required in order to delete functionality that was part of the public API in any release with the same MAJOR version number? E.g. change "8. Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API. It MAY also include minor and patch level changes. Patch and minor version MUST be reset to 0 when major version is incremented." to "8. Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API of any previous release with the same major version. A new major version MAY also include minor and patch level changes. Patch and minor version MUST be reset to 0 when major version is incremented." (Or alternatively, if the intent is to allow functionality to be deprecated and then deleted in successive minor releases without a new major release, can we clarify the wording to make that clear?)
Author
Owner

@Tieske commented on GitHub (May 27, 2021):

"if any backwards incompatible changes are introduced" fully covers removing functionality no?

<!-- gh-comment-id:849821217 --> @Tieske commented on GitHub (May 27, 2021): "_if any backwards incompatible changes are introduced_" fully covers removing functionality no?
Author
Owner

@fergushenderson commented on GitHub (May 27, 2021):

"if any backwards incompatible changes are introduced" fully covers removing functionality no?

Yes, "backwards incompatible changes" covers removing functionality.
But it's not clear which version(s) it should be applied to.
Does it mean changes that are backwards incompatible with

(a) the most recent released version?

(b) any released version?

(c) any released version with the same major version number?

Either (a) or (b) would be reasonable interpretations from the current text.
The correct answer -- the answer that gives the consequences that we want and expect -- is (c),
but as far as I can tell, this answer can't be inferred from the current text of SemVer 2.0.

For example, suppose we have the following sequence of releases:

  • version 1.0 introduces feature "foo"

  • version 1.1 deprecates "foo"

  • version 1.2 deletes feature "foo".

In this scenario, version 1.2 is backwards incompatible with the public API of 1.0, but is NOT incompatible with the public API of 1.1. Does this comply with SemVer 2.0?

Interpretation (a) implies NO, because 1.0 introduces a backwards incompatible change to the public API of 1.0, and doesn't increment the major version.

Interpretation (b) implies YES, because 1.2 does not introduce a backwards incompatible change to the public API of the most recent release (1.1). This is an undesirable conclusion.

Next, suppose we have a different sequence of releases:

  • version 1.0 introduces feature "foo"

  • version 1.1 deprecates "foo"

  • version 2.0 introduces feature "bar"

  • version 2.1 deletes feature "foo".

In this second scenario, version 2.1 is backwards incompatible with the public API of 1.0, but is NOT incompatible with the public API of 2.0. Does this comply with SemVer 2.0?

Interpretation (a) implies NO, because 2.1 "introduces a backwards incompatible change to the public API" (of 1.0), but doesn't increment the major version (compared to the previous release 2.0). This is an undesirable conclusion.

Interpretation (b) implies YES, because 2.1 does not introduce a backwards incompatible change to the public API of the most recent release (2.0), so it doesn't need to increment the major version number.

So both interpretation (a) and interpretation (b) have undesirable consequences for different scenarios.

Interpretation (c) is the interpretation that we want. But I don't see how it can be inferred from the current text.
Hence I think we should change the text.

<!-- gh-comment-id:849988647 --> @fergushenderson commented on GitHub (May 27, 2021): > "if any backwards incompatible changes are introduced" fully covers removing functionality no? Yes, "backwards incompatible changes" covers removing functionality. But it's not clear which version(s) it should be applied to. Does it mean changes that are backwards incompatible with (a) the most recent released version? (b) any released version? (c) any released version with the same **major** version number? Either (a) or (b) would be reasonable interpretations from the current text. The correct answer -- the answer that gives the consequences that we want and expect -- is (c), but as far as I can tell, this answer **can't be inferred from the current text of SemVer 2.0**. For example, suppose we have the following sequence of releases: - version 1.0 introduces feature "foo" - version 1.1 deprecates "foo" - version 1.2 deletes feature "foo". In this scenario, version 1.2 is backwards incompatible with the public API of 1.0, but is NOT incompatible with the public API of 1.1. Does this comply with SemVer 2.0? Interpretation (a) implies NO, because 1.0 introduces a backwards incompatible change to the public API of 1.0, and doesn't increment the major version. Interpretation (b) implies YES, because 1.2 does not introduce a backwards incompatible change to the public API of the most recent release (1.1). This is an undesirable conclusion. Next, suppose we have a different sequence of releases: - version 1.0 introduces feature "foo" - version 1.1 deprecates "foo" - version 2.0 introduces feature "bar" - version 2.1 deletes feature "foo". In this second scenario, version 2.1 is backwards incompatible with the public API of 1.0, but is NOT incompatible with the public API of 2.0. Does this comply with SemVer 2.0? Interpretation (a) implies NO, because 2.1 "introduces a backwards incompatible change to the public API" (of 1.0), but doesn't increment the major version (compared to the previous release 2.0). This is an undesirable conclusion. Interpretation (b) implies YES, because 2.1 does not introduce a backwards incompatible change to the public API of the most recent release (2.0), so it doesn't need to increment the major version number. So both interpretation (a) and interpretation (b) have undesirable consequences for different scenarios. Interpretation (c) is the interpretation that we want. But I don't see how it can be inferred from the current text. Hence I think we should change the text.
Author
Owner

@Tieske commented on GitHub (May 28, 2021):

I think your problem statement can be reworded;

The spec assumes that the order of the version-numbers aligns with the order in the time they are published. Since the wording of the spec is relative to the "previous version". It is however not clear how the "previous version" is defined.

For example:

  • 1.0.0 released yesterday
  • 2.0.0 released today
  • 1.0.1 released tomorrow (previous in "time" is 2.0.0, previous in "versions" is 1.0.0)
<!-- gh-comment-id:850074652 --> @Tieske commented on GitHub (May 28, 2021): I think your problem statement can be reworded; The spec assumes that the order of the version-numbers aligns with the order in the time they are published. Since the wording of the spec is relative to the "previous version". It is however not clear how the "previous version" is defined. For example: - 1.0.0 released yesterday - 2.0.0 released today - 1.0.1 released tomorrow (previous in "time" is 2.0.0, previous in "versions" is 1.0.0)
Author
Owner

@jwdonahue commented on GitHub (Sep 5, 2021):

Starting with your example:

version 1.0 introduces feature "foo"
version 1.1 deprecates "foo"
version 1.2 deletes feature "foo".

That last release was not SemVer compliant. It should have bumped to 2.0.0 as you removed foo.

version 1.0 introduces feature "foo"
version 1.1 deprecates "foo"
version 2.0 introduces feature "bar"
version 2.1 deletes feature "foo".

Technically not SemVer compliant, foo still exists in 2.0.0 and it's possible that your customers have taken dependencies on it. Introducing feature bar did not warrant a major version bump, but it is your prerogative to change version numbers at will. While it is not necessary to issue a 2.y.z deprecation notice before bumping to 3.0.0 for removal of foo, I would recommend it.

The way it's supposed to work is, that any breaking change from the previous version, must get a major version bump. Version histories can be a bit murky however, as publishers can remove versions from package services and modify change history files, changing the apparent history of the product. Your goal should be to avoid shocking/surprising your customer base, taking into consideration that some consumers of 2.0.0 may not have ever been aware of 1.1.0, but may take dependency on foo.

Here's what one legal scenario might look like

1.0.0 introduces foo.
...
1.3.0 deprecates foo.
2.0.0 removed foo.
1.4.0 add bar.
2.1.0 add bar.

Note that many publishers actively support several major version sequences simultaneously.

<!-- gh-comment-id:913201179 --> @jwdonahue commented on GitHub (Sep 5, 2021): Starting with your example: > version 1.0 introduces feature "foo" > version 1.1 deprecates "foo" > version 1.2 deletes feature "foo". That last release was not SemVer compliant. It should have bumped to 2.0.0 as you removed `foo`. > version 1.0 introduces feature "foo" > version 1.1 deprecates "foo" > version 2.0 introduces feature "bar" > version 2.1 deletes feature "foo". Technically not SemVer compliant, `foo` still exists in 2.0.0 and it's possible that your customers have taken dependencies on it. Introducing feature `bar` did not warrant a major version bump, but it is your prerogative to change version numbers at will. While it is not necessary to issue a 2.y.z deprecation notice before bumping to 3.0.0 for removal of `foo`, I would recommend it. The way it's supposed to work is, that any breaking change from the previous version, must get a major version bump. Version histories can be a bit murky however, as publishers can remove versions from package services and modify change history files, changing the apparent history of the product. Your goal should be to avoid shocking/surprising your customer base, taking into consideration that some consumers of 2.0.0 may not have ever been aware of 1.1.0, but may take dependency on `foo`. Here's what one legal scenario might look like 1.0.0 introduces `foo`. ... 1.3.0 deprecates `foo`. 2.0.0 removed `foo`. 1.4.0 add `bar`. 2.1.0 add `bar`. Note that many publishers actively support several major version sequences simultaneously.
Author
Owner

@dylanopen commented on GitHub (Jul 23, 2025):

Yes, you should increment the major version.

Basically, if you increment the minor or patch version, any possible use case of your library should still compile and work fine. Even if there's a small edge-case where your change breaks even just a small number of programs, you should always increase the major version.

Don't be scared of increasing the major version!! It's much better to release 50 breaking changes in 20 small major updates, rather than 50 breaking changes in a single major update, as if it's in one major update, it's very difficult for users of your library to update.

<!-- gh-comment-id:3106940097 --> @dylanopen commented on GitHub (Jul 23, 2025): Yes, you should increment the major version. Basically, if you increment the minor or patch version, any possible use case of your library should still compile and work fine. Even if there's a small edge-case where your change breaks even just a small number of programs, you should always increase the major version. Don't be scared of increasing the major version!! It's much better to release 50 breaking changes in 20 small major updates, rather than 50 breaking changes in a single major update, as if it's in one major update, it's very difficult for users of your library to update.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/semver#4723