21

We have a Java project. We enable -Xlint (enable warnings) and -Werror (treat warning as error) flags for javac, to make sure our code is warning-free. Recently we decide to deprecate a class. The problem is in some cases @SuppressWarnings("deprecation") will not suppress the deprecation warning at all, resulting in build failure. Below is a list of use cases that I ran into:

  1. Imported in other non-deprecated classes.
  2. Imported in other deprecated classes.
  3. Parent class.
  4. Type parameter. For example

    @SuppressWarnings("deprecation")
    public class Foo extends Bar<DeprecatedClass>
    { ... }
    

    However, this one has no warning even without suppress:

    @Deprecated
    public class DeprecatedClass extends Bar<DeprecatedClass>
    { ... }
    

AFAIK, there is no syntax for annotating imports, so for case 1 and 2 our solution is to either import * or avoid importing. For case 3 and 4, both Java 6 and 7 do not suppress the warning. Java 8 will correctly suppress it (maybe a bug is fixed). So far no solution for this.

Unfortunately, we have to support Java 6, 7 and 8 at this point. Is there way to deal with the problem? It is a road block for our Java API evolution.

ADDENDUM

Many people ask why do we still use the deprecated class in our own codebase. The reason is that the project is a library, supporting many different clients. When introducing new replacement API, we have to first deprecate our old API, keep it in our codebase, wait for all clients to migrate then remove it. There are three common use cases:

  • We deprecate class Foo and Bar, where Foo extends Bar. This is the case 2 and 3 in my question.
  • We deprecate class Foo and Bar, where Foo extends Collection<Bar>. This is the case 2 and 4.
  • We must keep all test code for class Foo and Bar. The test code imports these classes. This is the case 1.

Why keep the test? Don't forget that if a serious bug (e.g. memory leak, security issue) is discovered, and the clients can't easily migrate to the new version, we still need to provide bug fix to the old API. And all changes must be tested.

I feel our situation should be fairly common in software library development and API evolution. Surprisingly it took Java such long time (until Java 8) to fix the bug.

Reci
  • 4,099
  • 3
  • 37
  • 42
  • 2
    There are probably not many options. You could change your build process to do no `-Werror` unless target == 8, or exclude deprecation warnings since that seems to be broken in 6 and 7. Or you don't do standard deprecation (imo a bad alternative): Skip deprecation and remove the bad class right away, "deprecate" it by putting a note out on the web/javadoc, or in case that works also deprecate every class that uses it as type parameter even if it would stay in case the deprecated class would disappear. Also http://stackoverflow.com/a/20909204/995891 regarding 1) & 2) – zapl Nov 14 '14 at 02:39
  • I think not deprecate case 3 and put `@deprecated` in Javadoc might be the last resort, if no better approach exists. – Reci Nov 14 '14 at 03:00
  • Since you are referencing it as type rather than class, can you extend using generic type, and cast to `DeprecatedClass` later on, with a warning suppression around the cast? – CharlieS Nov 14 '14 at 03:32
  • How does that help? I think Java generics mainly provide compile time type checking. Replacing it with something like `Object` defeats the original purpose. – Reci Nov 15 '14 at 02:00
  • is DeprecatedClass in your imports? I find that it works if you have the supression at the top of the class, remove the deprecated class from the imports,and use the full package name of the deprecated class in the code. – jtahlborn Nov 15 '14 at 02:04
  • Would you consider full refactoring instead of deprecation? – Artem Nov 16 '14 at 18:26
  • If you are deprecating the class, there is presumably a replacement that supersedes `DeprecatedClass`. Can you refactor your code to use that newer class, thus avoiding this problem? – Duncan Jones Nov 17 '14 at 08:46
  • I've just edited your title, since the original one didn't reflect the question. You already know *how* to deprecate a class (although perhaps you shouldn't have, if there isn't a replacement). Your issue is about avoiding warnings when you use a deprecated class. – Duncan Jones Nov 17 '14 at 09:08
  • This is just a guess so I didn't make it an answer, but couldn't you move the classes into a sub-package, create package-info.java file in that package and put the @Deprecated in there, that should stop the warnings. – awm Nov 17 '14 at 10:41
  • @awm, unfortunately, moving package is backward incompatible and we can't do that. Adding/suppressing annotations, on the other hand, is both source and binary compatible. That's why we choose this approach. – Reci Nov 18 '14 at 02:21

3 Answers3

12

I'm sorry to say that I don't have a solution to the problem you're facing, though as you've observed, there has been some progress. We've been trying to get rid of all the Java compilation warnings in the JDK itself, and this has been a long, difficult process. During JDK 8 development in 2011 I helped kick off the warnings cleanup effort and I later co-presented a JavaOne talk (slides and audio) on the subject.

More recently, my colleage Joe Darcy has continued the warnings cleanup work and has worked through the different warnings categories and has finally reached deprecation warnings. As you noted, there have been some bugs in the compiler's handling of suppression of deprecation warnings, such as JDK-6480588 which was fixed in JDK 8. Unfortunately, it is still not possible in JDK 8 to suppress warnings on imports of deprecated items. This bug, JDK-8032211, was fixed quite recently in our JDK 9 development line. In fact, we're still tuning up the handling of the @Deprecated annotation. For example, bug JDK-6481080 clarifies that attempting to use @Deprecated in a package-info.java file does not in fact deprecate the package; this bug was fixed just last week. And there is more work to be done but it's somewhat speculative at this point.

The JDK is facing similar problems to yours, in that we have to maintain deprecated APIs for clients that are still using them. But since we use and implement such APIs internally, we have a lot of deprecation warnings to suppress. As of this writing, in our JDK 9 development line, we still have not been able to compile the system without deprecation warnings. As a result, the javac options for lint warnings are still:

-Xlint:all,-deprecation

You will probably have to disable deprecation warnings in your compilation as well, especially if you are still building on JDK 6. I don't see a way around it at this point.

One final note on one of your deprecation cases:

@Deprecated
public class DeprecatedClass extends Bar<DeprecatedClass> { ... }

This does not issue a deprecation warning, nor should it. The Java Language Specification, section 9.6.4.6, specifies that deprecation warnings are not issued if the use of a deprecated entity is within an entity that is itself deprecated.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
1

Consider using -Xmaxwarns, you can control how many warnings before stop.

Or try collect the number of warnings and fail the integration process, not compiling.

For example: https://issues.apache.org/jira/browse/HADOOP-11252. Every code commit to the hadoop project need to pass the automated CI and it give -1 for increase number of warnings.

Bochun Bai
  • 81
  • 5
-4

Normally, when you deprecate class, you don't want anybody to use it in later versions. Also, YOUR codebase should stop using deprecated class too. It looks strange, when you say everybody not to use MySuperDeprecatedUtil class but continue using it in your codebase.

If you need to use your MySuperDeprecatedUtil class in some other class - you should mark class where you use it as @Deprecated - every class that used deprecated code should be either deprecated, or would produce compilation warnings, or should be removed, or should stop using deprecated code.

If you can't stop using your class - maybe it's too early to deprecate it?

In my practice, when I want to deprecate some class, I create replacement class e.g. MySuperFreshUtil. Switch all classess using MySuperDeprecatedUtil to MySuperFresh util preserving interfaces where possible(if not possible - use FQCN and mark method as deprecated). Mark MySuperDeprecatedUtil as @Deprecated and add comment which class and how should be used instead. Then I commit this changes in single changelist.

Dmitry Zvorygin
  • 473
  • 6
  • 14
  • 2
    Suppose you have 10 implementations of an abstraction of a file format, for backwards compatibility. People should only use the latest format, so you deprecate the other 9. The factory that creates the right object for a given file still has to create instances of the other 9. You can't just remove the method, because it would break backwards compatibility. You also can't deprecate the factory method, because it's still used when reading the files. So by this logic, the only solution is to unmark the 9 old implementations so they're no longer deprecated. I'm not sure I would want to. – Hakanai Nov 01 '16 at 02:35
  • @Trejkaz or you split the reading and writing of the old format into separate classes and only deprecate the ones for writing… – Raphael Schweikert Aug 28 '20 at 11:18
  • @RaphaelSchweikert true, but it isn't always our API. :( – Hakanai Aug 29 '20 at 19:42