JEP 277: Enhanced Deprecation
Owner | Stuart Marks |
Type | Feature |
Scope | SE |
Status | Closed / Delivered |
Release | 9 |
Component | core-libs / java.lang |
Discussion | jdk9 dash dev at openjdk dot java dot net |
Effort | M |
Duration | M |
Reviewed by | Alex Buckley, Mark Reinhold |
Endorsed by | Brian Goetz |
Created | 2014/11/20 23:58 |
Updated | 2017/12/08 23:29 |
Issue | 8065614 |
Summary
Revamp the @Deprecated
annotation, and provide tools to strengthen the
API life cycle.
Goals
-
Provide better information about the status and intended disposition of APIs in the specification.
-
Provide a tool to analyze an application's static usage of deprecated APIs.
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:
-
A method
forRemoval()
returning aboolean
. Iftrue
, it means that this API element is earmarked for removal in a future release. Iffalse
, the API element is deprecated, but there is currently no intention to remove it in a future release. The default value of this element isfalse
. -
A method named
since()
returningString
. This string should contain the release or version number at which this API became deprecated. It has free-form syntax, but the release numbering should follow the same scheme as the@since
Javadoc tag for the project containing the deprecated API. Note that this value is not redundant with the Javadoc@since
tag, because that records the release in which the API was introduced, whereas thesince()
method in a@Deprecated
annotation records the release in which the API was deprecated. The default value of this element is the empty string.
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 API is flawed and is impractical to fix,
-
usage of the API is likely to lead to errors,
-
the API has been superseded by another API,
-
the API is obsolete,
-
the API is experimental and is subject to incompatible changes,
-
or any combination of the above.
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. The changes implemented in Java SE 9 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.
-
add
@Deprecated
to constructors for boxed primitives (Boolean
,Integer
, etc.) (JDK-8145468) -
add
@Deprecated(forRemoval=true)
to theRuntime.traceInstructions
andRuntime.traceMethodCalls
methods (JDK-8153330) -
add
@Deprecated
to variousjava.applet
and related classes (JEP 289) -
add
@Deprecated
tojava.util.Observable
andObserver
(JDK-8154801) -
add
@Deprecated(forRemoval=true)
to various superseded security APIs, includingjava.security.acl
(JDK-8157847),javax.security.cert
andcom.sun.net.ssl
(JDK-8157712),java.security.Certificate
(JDK-8157707), andjavax.security.auth.Policy
(JDK-8157848) -
add
@Deprecated(forRemoval=true)
tojava.lang.Compiler
(JDK-4285505) -
add
@Deprecated
to several Java EE modules and thejava.corba
module (JDK-8169069, JDK-8181195, JDK-8181702, JDK-8174728) -
modify already-deprecated methods
Thread.destroy()
,Thread.stop(Throwable)
,Thread.countStackFrames()
,System.runFinalizersOnExit()
, and various disusedRuntime
andSecurityManager
methods to have@Deprecated(forRemoval=true)
(JDK-8145468)
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:
- Use site X depends on API Y, currently not deprecated
- Y's declaration changes to ordinary deprecation, generating ordinary deprecation warning at X
- X is annotated with
@SuppressWarnings("deprecation")
, suppressing the warning - Y's declaration changes to terminal deprecation; removal warning at X still suppressed
- 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:
-
java.deprecation.enableLogging
— boolean, defaultfalse
If true, as determined by the
Boolean.parseBoolean
method, then library code will log deprecation messages. Messages will be logged using a logger obtained by callingSystem.getLogger()
, and messages will be logged using a level ofSystem.Logger.Level.WARNING
. -
java.deprecation.enableStackTrace
— boolean, defaultfalse
If true, and if deprecation logging is enabled, log messages will include a stack trace.
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.