JEP 277: Enhanced Deprecation

OwnerStuart Marks
Created2014/11/20 23:58
Updated2016/12/21 18:15
TypeFeature
StatusCompleted
Componentcore-libs / java.lang
ScopeSE
Discussionjdk9 dash dev at openjdk dot java dot net
EffortM
DurationM
Priority2
Reviewed byAlex Buckley, Mark Reinhold
Endorsed byBrian Goetz
Release9
Issue8065614

Summary

Revamp the @Deprecated annotation, and provide tools to strengthen the API life cycle.

Goals

Non-Goals

It is not a goal of this project to unify the @deprecated Javadoc tag with the @Deprecated annotation.

Motivation

Deprecation is a technique to communicate information about the life cycle of an API: to encourage applications to migrate away from the API, to discourage applications from forming new dependencies on the API, and to inform developers of the risks of continuing dependence upon the API.

Java offers two mechanisms to express deprecation: the @deprecated Javadoc tag, introduced in JDK 1.1, and the @Deprecated annotation, introduced in Java SE 5. The API specification for the @Deprecated annotation, mirrored in The Java Language Specification, is:

A program element annotated @Deprecated is one that programmers are discouraged from using, typically because it is dangerous, or because a better alternative exists. Compilers warn when a deprecated program element is used or overridden in non-deprecated code.

However, the @Deprecated annotation ended up being used for several different purposes. Very few deprecated APIs were actually removed, leading some people to believe that nothing would ever be removed. On the other hand, other people believed that everything that was deprecated might eventually be removed, which was never the intent either. (Although it wasn't stated explicitly in the specifications, various documents mentioned that deprecated APIs would be removed at some point.) This resulted in an unclear message being delivered to developers about the meaning of @Deprecated, and what, if anything, developers should do when they encountered usage of a deprecated API. Everybody was confused about what deprecation actually meant, and nobody took it seriously. This in turn has made it difficult ever to remove anything from the Java SE API.

Another problem with deprecation is that warnings are issued only at compile time. As APIs become deprecated in successive versions of Java SE, existing binaries continue to depend and use the deprecated APIs with no warnings. If a deprecated API were to be removed in a JDK release, even after one or more releases where it was deprecated, this would come as an unpleasant surprise to users of old application binaries. The application would suddenly fail with a linkage error, with no warnings having ever been emitted. Worse, there is no means for developers to check whether existing binaries have any dependencies on deprecated APIs. This causes significant tension between the ability to run old binaries on new JDK releases versus the need to evolve the specification through the retirement of old APIs.

In summary, the deprecation mechanisms have been applied inconsistently in the Java SE API, resulting in confusion about the meaning of deprecation in principle and the proper use of deprecation in practice.

Description

Specifications

The primary purpose of enhancing the @Deprecated annotation is to provide finer-grained information to tools about the deprecation status of an API. These tools in turn use the annotation to report information to users of the API. The @Deprecated annotation has runtime retention and therefore consumes heap memory. The information here should therefore be minimal and well-specified.

The following elements are to be added to the java.lang.Deprecated annotation type:

Since these elements are being added to the existing @Deprecated annotation, annotation processing programs will see the default values for forRemoval() and since() if they are processing a class file that was compiled with a version of @Deprecated older than JDK 9.

The presence of the @Deprecated annotation on an API is communication from the author or maintainer of the API to users of the API. Most generally, deprecation is advice that users migrate their usage away from the deprecated API, that they avoid adding dependencies on this API from new code or while maintaining old code, or that there is a certain amount of risk in maintaining code that depends on this API. There are many reasons to recommend such migration. Reasons might include the following:

The exact reasons for deprecating an API are often too subtle to be expressed as flags or element values in the annotation. It is strongly recommended that the reasons for deprecating an API be described in that API's documentation comments. In addition, it is also recommended that potential replacement APIs be discussed and linked from the documentation.

One specific flag value is provided, however. The forRemoval() boolean element, if true, indicates intent that the API element is to be removed in some future release of the project. Users of the API are thus given advance warning that, if they don't migrate away from the API, their code is liable to break when upgrading to a newer release. If forRemoval() is false, this indicates a recommendation to migrate away from the deprecated API, but without any specific intent to remove that API.

The @Deprecated annotation and the @deprecated javadoc tag should both be present or both be absent on an API element. The presence of one without the other is considered to be a mistake. The javac lint flag -Xlint:dep-ann will issue warnings if the @deprecated tag is present on an API that lacks the @Deprecated annotation. There is currently no warning if the reverse is true; see JDK-8141234.

The @Deprecated annotation should have no direct impact on the behavior of deprecated APIs, and there should negligible performance impact.

Usage in Java SE

The @Deprecated annotation type appears in Java SE, and thus it may be applied to the APIs any class library that uses the Java SE platform. The exact rules and policies for how those class libraries use the @Deprecated annotation type is a matter for the maintainers of those libraries to determine. It is recommended that class library maintainers develop and document such policies.

This section describes the uses of the @Deprecated annotation type on Java SE APIs themselves and also the policies governing such use.

Several Java SE APIs will have a @Deprecated annotation added, updated, or removed. Some proposed changes are listed below. Unless otherwise specified, the deprecations listed here are not for removal. Note that this is not a comprehensive list of deprecations in Java SE 9. Also note that several of these items will not be implemented in Java SE 9. Check the status of the linked bug for details. (If there is no linked bug, the deprecation has not been implemented.)

Given the history of deprecation in Java SE, and the emphasis on long term API compatibility across versions, removal of an API is a matter of serious concern. Therefore, deprecation with the element forRemoval=true should be applied only when there is a clear and definite plan for removing that API in the next release of the Java SE platform.

An API element should not be removed from the Java SE specification unless it has been delivered with an annotation of @Deprecated(forRemoval=true) in a previous version of Java SE. It is acceptable for a deprecation to be introduced with forRemoval=true. It isn't necessary to first deprecate with forRemoval=false, then upgrade to forRemoval=true, before removing the API.

For API elements deprecated in Java SE 9 and beyond, the since element should contain the Java SE version string denoting the version in which the API element was deprecated. The version string should conform to the format specified in JEP 223. Since Java SE typically makes specification changes only in major releases, the version string will often consist solely of the "MAJOR" version number. Thus, for API elements deprecated in Java SE 9, the since element value should simply be "9".

API elements that had been deprecated prior to Java SE 9 will have their since value filled in only as time permits. (Doing this for all APIs is of marginal value and is mainly an exercise in historical research.) The string used for the since value in such cases should conform to the JDK version conventions used for the @since javadoc tag for those releases, typically 1.0 through 1.8 but sometimes with a "micro" release number, such as 1.0.2. Annotation processing tools looking for this value on Java SE APIs and finding an empty string should assume that the deprecation occurred in Java SE 8 or earlier.

Deprecating APIs will increase the number of mandatory warnings that projects encounter when building against newer versions of Java SE. Some projects, including the JDK itself, build with compiler options that enable verbose warnings and that turn warnings into errors. For such projects, adding deprecated APIs to Java SE can introduce a large number of warnings, adding significantly to the effort of migrating to a new version of Java SE. Existing mechanisms for managing warnings, such the @SuppressWarnings annotation and compiler command-line options, are insufficient for dealing with this issue. This effectively places a limit on which APIs can be deprecated in a given Java SE release, and it makes deprecation of obsolete but popular APIs nearly impossible. This calls for a future effort to enhance the mechanisms available to manage deprecation warnings.

Impact of forRemoval on Warning Policy

The Java Language Specification, section 9.6.4.6 mandates specific warning behaviors that depend upon the deprecation status of an API that is being depended upon (the "declaration site"), in combination with the deprecation status of the code that is using that API (the "use site"). The addition of the forRemoval element adds another set of cases that must be defined. For the sake of brevity, we will refer to a deprecation with forRemoval=false as an "ordinary deprecation" and a deprecation with forRemoval=true as a "terminal deprecation."

In Java SE 8 and earlier, forRemoval did not exist, so the only kind of deprecations were ordinary deprecations. Whether a deprecation warning was issued depended upon the deprecation status of both the use site and the declaration site. Here is a table of cases that existed in Java SE 8:

use site     | API declaration site
    context      | not dep.   deprecated
                 +-----------------------
    not dep.     |    N          W
                 |
    deprecated   |    N          N (1)

        N = no warning
        W = warning

(Note 1) This is an odd case. If the use and declaration site are both deprecated, no warning is issued. This makes sense if both sites are within a single class library that is maintained and released as a unit. Since they are maintained together, there is little point in issuing a warning in this case. However, if the use site is within a class library that is maintained separately from the declaration site, they may evolve at different rates, and so not issuing a warning in this case is likely to be a misfeature. However, this mechanism was useful for reducing the number of warnings from compilation of the JDK, prior to the introduction of the @SuppressWarnings annotation in Java SE 5.

(JLS 9.6.4.6 also requires no warnings to be issued if the use site is within the same outermost class as the declaration site. In such cases the use and declaration sites are by definition maintained together, so the rationale for not issuing a warning applies well.)

In Java SE 9, the introduction of forRemoval adds several new cases having to do with terminal deprecation. This requires the introduction of a new kind of warning.

Warnings issued at the point of use of an ordinarily deprecated API are "ordinary deprecation warnings" which are the same as in Java SE 8 and earlier. These are often simply called "deprecation warnings" as a holdover from previous usage.

Warnings issued at the point of use of a terminally deprecated API might formally be called "terminal deprecation warnings" but this is rather verbose. Instead we will refer to such warnings as "removal warnings".

The proposed table of cases is shown below:

use site     |      API declaration site
    context      | not dep.   ord. dep.   term. dep.
                 +----------------------------------
    not dep.     |    N         oW (2)       rW (5)
                 |
    ord. dep.    |    N          N (3)       rW (6)
                 |
    term. dep.   |    N          N (4)       rW (7)

(Note 2) "oW" refers to an "ordinary deprecation warning" which is the same kind of warning that has occurred in this case in Java SE 8 and earlier.

(Note 3) The upper left four elements are the same as in the Java SE 8 table, for reasons of backward compatibility.

(Note 4) No warning is issued here by extrapolating from compatible behavior. If both use and declaration site are both ordinarily deprecated, it would be perverse if changing the use site to be terminally deprecated were to introduce a warning. Thus, no warning is issued in this case.

(Note 5) "rW" refers to a "removal warning". All warnings issued at use sites of terminally deprecated APIs are removal warnings.

(Note 6) This case is quite significant. We always want the use of a terminally deprecated API to generate a removal warning, even if the use site is within deprecated code.

(Note 7) This is similar to (6). One might think that, since both the use and declaration sites are terminally deprecated, both are "going away" and that it would be pointless to issue a warning here. But the possibility is that the declaration site is within a library that is evolving more quickly than the use site, so the use site might outlive the declaration site. Therefore, a warning about the impending removal of the declaration site is necessary.

The general rule that covers the lower right four elements is as follows. If the use site is deprecated, whether ordinarily or terminally, no ordinary deprecation warnings will be issued, but removal warnings will still be issued.

An example of an ordinary deprecation warning might be as follows:

UseSite.java:3: warning: [deprecation] ordinary() in DeclSite has been deprecated

An example of a removal warning might be as follows:

UseSite.java:4: warning: [removal] removal() in DeclSite has been deprecated and marked for removal

The specific wording of the warnings, and the mechanisms for customization of warnings, may differ from compiler to compiler.

Suppression of Deprecation Warnings

In Java SE 8 and earlier, it was possible to suppress deprecation warnings by annotating the use site with @SuppressWarnings("deprecation"). This behavior needs to be modified in the presence of terminal deprecation.

Consider a case where a use site depends on an API that is ordinarily deprecated, and that the resulting warning has been suppressed with a @SuppressWarnings("deprecation") annotation. If the declaration site were to be modified to be terminally deprecated, we would want a removal warning to occur at the use site, even though warnings at the use site have already been suppressed. If a new warning were not issued in this case, it would be possible for an API to be terminally deprecated and then removed without any warnings at its use sites.

The following scenario illustrates the problem. Suppose that the @SuppressWarnings("deprecation") annotation were to suppress both ordinary deprecation warnings as well as removal warnings. Then, the following could occur:

  1. Use site X depends on API Y, currently not deprecated
  2. Y's declaration changes to ordinary deprecation, generating ordinary deprecation warning at X
  3. X is annotated with @SuppressWarnings("deprecation"), suppressing the warning
  4. Y's declaration changes to terminal deprecation; removal warning at X still suppressed
  5. Y is removed entirely, causing X to break unexpectedly

Inasmuch as the purpose of deprecation is to communicate information about API evolution, particularly about removal of APIs, the lack of any warning in this case is a serious problem. It follows that a warning should be given when a deprecation is "upgraded" from an ordinary to a terminal deprecation, even if the warnings at that use site had previously been suppressed.

We need a mechanism for suppressing removal warnings that differs from the mechanism currently used for suppressing ordinary deprecation warnings. The solution is to use a different string in the @SuppressWarnings annotation.

Removal warnings -- warnings that arise from the use of terminally deprecationed APIs -- can be suppressed with the annotation

@SuppressWarnings("removal")

This annotation suppresses only removal warnings, and not ordinary deprecation warnings. We considered making this be a strong form of suppression that would cover both ordinary deprecation warnings and removal warnings. However, this potentially leads to errors. Programmers might use @SuppressWarnings("removal") to suppress warnings from ordinary deprecations. This would prevent warnings from appearing if an ordinary deprecation were changed to a terminal deprecation, leading to unexpected breakage when the terminally deprecated API is eventually removed.

As before, warnings from the use of ordinarily deprecated APIs can be suppressed with the annotation

@SuppressWarnings("deprecation")

As noted above, this annotation suppresses only ordinary deprecation warnings; it doesn't suppress removal warnings.

If it is necessary to suppress both ordinary deprecation warnings and removal warnings at a particular site, the following construct can be used:

@SuppressWarnings({"deprecation", "removal"})

Below is a copy of the warnings table from the previous section, modified to show how warnings from the different cases can be suppressed.

use site     |      API declaration site
    context      | not dep.   ord. dep.   term. dep.
                 +----------------------------------
    not dep.     |    -        @SW(d)       @SW(r)
                 |
    ord. dep.    |    -           -         @SW(r)
                 |
    term. dep.   |    -           -         @SW(r)

        @SW(d) = @SuppressWarnings("deprecation")
        @SW(r) = @SuppressWarnings("removal")

If a removal warning is suppressed with @SuppressWarnings("removal") at the use site of a terminally deprecated API, and that API is changed to an ordinary deprecation, it is somewhat odd that an ordinary deprecation warning will appear. However, we expect the evolution path of an API from terminal deprecation back to ordinary deprecation to be quite rare.

JLS section 9.6.4.6 will need to be modified accordingly. That change is covered by JDK-8145716.

Static Analysis

A static analysis tool jdeprscan will be provided that scans a jar file (or some other aggregation of class files) for uses of deprecated API elements. By default, the deprecated APIs will be the deprecations from Java SE itself. A future extension will provide for the ability to scan for deprecations that have been declared in a class library other than Java SE.

Ideas for Future Work

A dynamic analysis tool jdeprdetect could be provided to track dynamic uses of deprecated APIs. It can be implemented by using a Java agent, instrumenting the deprecated API elements and issuing warning messages when usage of those elements is detected at runtime.

Dynamic analysis should be helpful at catching cases that static analysis misses. These cases include reflective access to deprecated APIs, or use of deprecated providers loaded via ServiceLoader. Furthermore, dynamic analysis can show the absence of a dependency that might be flagged by static analysis. For example, code might reference a deprecated API, and this reference will cause jdeprscan to emit a warning. However, if the code referencing a deprecated API is dead code, no warning will be emitted by jdeprdetect. This information should help developers prioritize their code migration efforts.

Certain features reside entirely within library implementations and aren't manifested in any public APIs. One example of this is the "legacy merge sort" algorithm. See Java SE 7 and JDK 7 Compatibility for further information. Library implementations of deprecated features should be able to check various system properties to determine whether to issue log messages at runtime, and if so, what form the log message should take. These properties might include:

Implementation and enhancements to other tools is beyond the scope of this JEP. A number of ideas for such tool enhancements are described here as suggestions for future work.

The javadoc tool could be enhanced to handle the detail code of a @Deprecated annotation. It could also provide a more prominent display of the Detail values. The handling of the @deprecated Javadoc tag should be largely unchanged, though perhaps it might be modified somewhat to include information about the forRemoval and since values.

The standard doclet could be modified to treat deprecated APIs differently. For example, deprecated members of a class might be put into a separate tab, along side the existing tabs for instance, abstract, and concrete methods. Deprecated classes could be moved to a separate section in the package frame. Currently, it contains sections for Interfaces, Classes, Enums, Exceptions, Errors, and Annotation Types. New sections for deprecated members could be added.

The list of deprecated APIs could be enhanced as well. (This page is reached via the link at the very top of each page, in the bar containing links Overview, Package, Class, Use, Tree, Deprecated, Index, Help.) This page is currently organized by kind: interfaces, classes, exceptions, annotation types, fields, methods, constructors, and annotation type elements. API elements that include the value forRemoval=true should be highlighted, as their impending removal potentially has great impact.

The enhanced @Deprecated annotation will impact other tools such as IDEs. For example, deprecated APIs should be absent from IDEs' auto-completion menus and dialogs by default. Or, automatic refactoring rules could be offered that replace calls to deprecated APIs with calls to their replacements.

Alternatives

A set of alternatives that has been proposed includes having the JVM halt, having deprecated features be disabled, or having usage of deprecated APIs cause a compile-time error, unless a version-specific option is supplied. All of these proposals will succeed only at notifying the developer of the first usage of a deprecated feature, because the normal program (or build) flow is interrupted at that point. Thus, subsequent uses of deprecated features would likely go undetected. Upon encountering such failures, most developers would simply supply the version-specific option to enable the deprecated features. Thus, in general, this approach won't be successful at providing developers information about all of the deprecated features in use by an application.

It has been suggested that the @deprecated Javadoc tag be retired in favor of the @Deprecated annotation. The @deprecated Javadoc tag and the @Deprecated annotation should always both be present or absent. However, they are redundant only in very abstract, conceptual sense. The @deprecated Javadoc tag provides descriptive text, rationale, and information and links to replacement APIs. This information is quite suitable for including in javadoc documentation, which already has facilities for it (such as link tags). Moving such textual information into annotation values would require javadoc to extract the information from annotations instead of doc comments. It would be harder for developers to maintain, since annotations have no markup support. Finally, annotation elements take up space at runtime, and it's unnecessary for documentation text to be present in memory at runtime.

A string value has been proposed as a detail code. This appears to provide more flexibility, but it also introduces problems with weak typing and namespace conflicts, possibly leading to undetected errors.

A "replacement" element in the @Deprecated annotation was present in earlier versions of this proposal. The intent was for it to denote a specific API that replaces the one being deprecated. In practice, there is never a drop-in replacement API for any deprecated API; there are always tradeoffs and design considerations, or choices to be made among several possible replacements. All of these topics require discussion and are thus better suited for textual documentation. Finally, there is no syntax for referring to another API from an annotation element, whereas Javadoc already supports such references via its @see and @link tags.

Previous versions of this proposal included a variety of "reason" codes including UNSPECIFIED, DANGEROUS, OBSOLETE, SUPERSEDED, UNIMPLEMENTED, and EXPERIMENTAL. These attempted to encode the reason for which an API was deprecated, the risks of using it, and also whether a replacement API is available. In practice, all of this information is too subjective be encoded as values in an annotation. Instead, this information should be described in the Javadoc documentation comment. The only significant bit of detail remaining is whether there is intent to remove the API. This is expressed in the forRemoval annotation element.

Testing

A reasonably simple set of tests will be constructed for the new tooling. A set of cases will be provided where each different kind of API element that can be deprecated is deprecated. Another set of cases will be constructed, consisting of usages of each deprecated API from the cases described above. The static analysis checker jdeprscan should be run to ensure that it issues warnings for all such usages.