JEP 277 “Enhanced Deprecation” is Nice. But Here’s a Much Better Alternative

Maintaining APIs is hard.

We’re maintaining the jOOQ API which is extremely complex. But we are following relatively relaxed rules as far as semantic versioning is concerned.

When you read comments by Brian Goetz and others about maintaining backwards-compatibility in the JDK, I can but show a lot of respect for their work. Obviously, we all wish that things like Vector, Stack, Hashtable were finally removed, but there are backwards-compatibility related edge cases around the collections API that ordinary mortals will never think of. For instance: Why aren’t Java Collections remove methods generic?

Better Deprecation

Stuart Marks aka Dr Deprecator

Stuart Marks aka Dr Deprecator

With Java 9, Jigsaw, and modularity, one of the main driving goals for the new features is to be able to “cut off” parts of the JDK and gently deprecate and remove them over the next releases. And as a part of this improvement, Stuart Marks AKA Dr Deprecator has suggested JEP 277: “Enhanced Deprecation”

The idea is for this to enhance the @Deprecated annotation with some additional info, such as:

  • UNSPECIFIED. This API has been deprecated without any reason having been given. This is the default value; everything that’s deprecated today implicitly has a deprecation reason of UNSPECIFIED.
  • CONDEMNED. This API is earmarked for removal in a future JDK release. Note, the use of the word “condemned” here is used in the sense of a structure that is intended to be torn down. The term is not mean to imply any moral censure.
  • DANGEROUS. Use of this API can lead to data loss, deadlock, security vulnerability, incorrect results, or loss of JVM integrity.
  • OBSOLETE. This API is no longer necessary, and usages should be removed. No replacement API exists. Note that OBSOLETE APIs might or might not be marked CONDEMNED.
  • SUPERSEDED. This API has been replaced by a newer API, and usages should be migrated away from this API to the newer API. Note that SUPERSEDED APIs might or might not be marked CONDEMNED.
  • UNIMPLEMENTED. Calling this has no effect or will unconditionally throw an exception.
  • EXPERIMENTAL. This API is not a stable part of the specification, and it may change incompatibly or disappear at any time.

When deprecating stuff, it’s important to be able to communicate the intent of the deprecation. This can be achieved as well via the @deprecated Javadoc tag, where any sort of text can be generated.

An alternative, much better solution

The above proposition suffers from the following problems:

  • It’s not extensible. The above may be enough for JDK library designers, but we as third party API providers will want to have many more elements in the enum, other than CONDEMNED, DANGEROUS, etc.
  • Still no plain text info. There is still redundancy between this annotation and the Javadoc tag as we can still not formally provide any text to the annotation that clarifies, e.g. the motivation of why something is “DANGEROUS”.
  • “Deprecated” is wrong. The idea of marking something UNIMPLEMENTED or EXPERIMENTAL as “deprecated” shows the workaround-y nature of this JEP, which tries to shoehorn some new functionality into existing names.

I have a feeling that the JEP is just too afraid to touch too many parts. Yet, there would be an extremely simple alternative that is much much better for everyone:

public @interface Warning {
    String name() default "warning";
    String description() default "";
} 

There’s no need to constrain the number of possible warning types to a limited list of constants. Instead, we can have a @Warning annotation that takes any string!

Of course, the JDK could have a set of well-known string values, such as:

public interface ResultSet {

    @Deprecated
    @Warning(name="OBSOLETE")
    InputStream getUnicodeStream(int columnIndex);

}

or…

public interface Collection<E> {

    @Warning(name="OPTIONAL")
    boolean remove(Object o);
}

Notice that while JDBC’s ResultSet.getUnicodeStream() is really deprecated in the sense of being “OBSOLETE”, we could also add a hint to the Collection.remove() method, which applies only to the Collection type, not to many of its subtypes.

Now, the interesting thing with such an approach is that we could also enhance the useful @SuppressWarnings annotation, because sometimes, we simply KnowWhatWeAreDoing™, e.g. when writing things like:

Collection<Integer> collection = new ArrayList<>();

// Compiler!! Stop bitching
@SuppressWarnings("OPTIONAL")
boolean ok = collection.remove(1);

This approach would solve many problems in one go:

  • The JDK maintainers have what they want. Nice tooling for gently deprecating JDK stuff
  • The not-so-well documented mess around what’s possible to do with @SuppressWarnings would finally be a bit more clean and formal
  • We could emit tons of custom warnings to our users, depending on a variety of use-cases
  • Users could mute warnings on a very fine-grained level

For instance: A motivation for jOOQ would be to disambiguate the DSL equal() method from the unfortunate Object.equals() method:

public interface Field<T> {

   /**
     * <code>this = value</code>.
     */
    Condition equal(T value);

    /**
     * <strong>Watch out! This is 
     * {@link Object#equals(Object)}, 
     * not a jOOQ DSL feature!</strong>
     */
    @Override
    @Warning(
        name = "ACCIDENTAL_EQUALS",
        description = "Did you mean Field.equal?"
    )
    boolean equals(Object other);
}

The background of this use-case is described here:
https://github.com/jOOQ/jOOQ/issues/4763

Conclusion

JEP 277 is useful, no doubt. But it is also very limited in scope (probably not to further delay Jigsaw?) Yet, I wish this topic of generating these kinds of compiler warnings would be dealt with more thoroughly by the JDK maintainers. This is a great opportunity to DoTheRightThing™

I don’t think the above “spec” is complete. It’s just a rough idea. But I had wished for such a mechanism many many times as an API designer. To be able to give users a hint about potential API misuse, which they can mute either via:

  • @SuppressWarnings, directly in the code.
  • Easy to implement IDE settings. It would be really simple for Eclipse, NetBeans, and IntelliJ to implement custom warning handling for these things.

Once we do have a @Warning annotation, we can perhaps, finally deprecate the not so useful @Deprecated

@Warning(name = "OBSOLETE")
public @interface Deprecated {
}

Discussions

See also follow-up discussions on:

Java 8 Friday: Let’s Deprecate Those Legacy Libs

At Data Geekery, we love Java. And as we’re really into jOOQ’s fluent API and query DSL, we’re absolutely thrilled about what Java 8 will bring to our ecosystem.

Java 8 Friday

Every Friday, we’re showing you a couple of nice new tutorial-style Java 8 features, which take advantage of lambda expressions, extension methods, and other great stuff. You’ll find the source code on GitHub.

For the last two Fridays, we’ve been off for our Easter break, but now we’re back with another fun article:

Let’s Deprecate Those Legacy Libs

d8938bef47ea2f62ed0543dd9e35a483Apart from Lambdas and extension methods, the JDK has also been enhanced with a lot of new library code, e.g. the Streams API and much more. This means that we can critically review our stacks and – to the great joy of Doctor Deprecator – throw out all the garbage that we no longer need.

Here are a couple of them, just to name a few:

LINQ-style libraries

There are lots of libraries that try to emulate LINQ (i.e. the LINQ-to-Collections part). We’ve already made our point before, because we now have the awesome Java 8 Streams API. 5 years from today, no Java developer will be missing LINQ any longer, and we’ll all be Streams-masters with Oracle Certified Streams Developer certifications hanging up our walls.

Don’t get me wrong. This isn’t about LINQ or Streams being better. They’re pretty much the same. But since we now have Streams in the JDK, why worry about LINQ? Besides, the SQLesque syntax for collection querying was misleading anyway. SQL itself is much more than Streams will ever be (or needs to be).

So let’s list a couple of LINQesque APIs, which we’ll no longer need:

LambdaJ

This was a fun attempt at emulating closures in Java through arcane and nasty tricks like ThreadLocal. Consider the following code snippet (taken from here):

// This lets you "close over" the
// System.out.println method
Closure println = closure(); { 
  of(System.out).println(var(String.class));
}

// in order to use it like so:
println.apply("one");
println.each("one", "two", "three");

Nice idea, although that semi-colon after closure(); and before that pseudo-closure-implementation block, which is not really a closure body… all of that seems quite quirky ;-)

Now, we’ll write:

Consumer<String> println = System.out::println;

println.accept("one");
Stream.of("one", "two", "three").forEach(println);

No magic here, just plain Java 8.

Let’s hear it one last time for Mario Fusco and Lambdaj.

Linq4j

Apparently, this is still being developed actively… Why? Do note that the roadmap also has a LINQ-to-SQL implementation in it, including:

Parser support. Either modify a Java parser (e.g. OpenJDK), or write a pre-processor. Generate Java code that includes expression trees.

Yes, we’d like to have such a parser for jOOQ as well. It would allow us to truly embed SQL in Java, similar to SQLJ, but typesafe. But if we have the Streams API, why not implement something like Streams-to-SQL?

We cannot say farewell to Julian Hyde‘s Linq4j just yet, as he’s still continuing work. But we believe that he’s investing in the wrong corner.

Coolection

This is a library with a fun name, and it allows for doing things like…

from(animals).where("name", eq("Lion"))
             .and("age", eq(2))
             .all();

from(animals).where("name", eq("Dog"))
             .or("age", eq(5))
             .all();

But why do it this way, when you can write:

animals.stream()
       .filter(a -> a.name.equals("Lion")
                 && a.age == 2)
       .collect(toList());

animals.stream()
       .filter(a -> a.name.equals("Dog")
                 || a.age == 5)
       .collect(toList());

Let’s hear it for Wagner Andrade. And then off to the bin

Half of Guava

Guava has been pretty much a dump for all sorts of logic that should have been in the JDK in the first place. Take com.google.guava.base.Joiner for instance. It is used for string-joining:

Joiner joiner = Joiner.on("; ").skipNulls();
. . .
return joiner.join("Harry", null, "Ron", "Hermione");

No need, any more. We can now write:

Stream.of("Harry", null, "Ron", "Hermione")
      .filter(s -> s != null)
      .collect(joining("; "));

Note also that the skipNulls flag and all sorts of other nice-to-have utilities are no longer necessary as the Streams API along with lambda expressions allows you to decouple the joining task from the filtering task very nicely.

Convinced? No?

What about:

And then, there’s the whole set of Functional stuff that can be thrown to the bin as well:

https://code.google.com/p/guava-libraries/wiki/FunctionalExplained

Of course, once you’ve settled on using Guava throughout your application, you won’t remove its usage quickly. But on the other hand, let’s hope that parts of Guava will be deprecated soon, in favour of an integration with Java 8.

JodaTime

Now, this one is a no-brainer, as the popular JodaTime library got standardised into the java.time packages. This is great news.

Let’s hear it for “Joda” Stephen Colebourne and his great work for the JSR-310.

Apache commons-io

The java.nio packages got even better with new methods that nicely integrate with the Streams API (or not). One of the main reasons why anyone would have ever used Apache Commons IO was the fact that it is horribly tedious to read files prior to Java 7 / 8. I mean, who would’ve enjoyed this piece of code (from here):

try (RandomAccessFile file = 
     new RandomAccessFile(filePath, "r")) {
    byte[] bytes = new byte[size];
    file.read(bytes);
    return new String(bytes); // encoding?? ouch!
}

Over this one?

List<String> lines = FileUtils.readLines(file);

But forget the latter. You can now use the new methods in java.nio.file.Files, e.g.

List<String> lines = Files.readAllLines(path);

No need for third-party libraries any longer!

Serialisation

Throw it all out, for there is JEP 154 deprecating serialisation. Well, it wasn’t accepted, but we could’ve surely removed about 10% of our legacy codebase.

A variety of concurrency APIs and helpers

With JEP 155, there had been a variety of improvements to concurrent APIs, e.g. to ConcurrentHashMaps (we’ve blogged about it before), but also the awesome LongAdders, about which you can read a nice article over at the Takipi blog.

Haven’t I seen a whole com.google.common.util.concurrent package over at Guava, recently? Probably not needed anymore.

JEP 154 (Serialisation) wasn’t real

It was an April Fools’ joke, of course…

Base64 encoders

How could this take so long?? In 2003, we’ve had RFC 3548, specifying Base16, Base32, and Base64 data encodings, which was in fact based upon base 64 encoding specified in RFC 1521, from 1993, or RFC 2045 from 1996, and if we’re willing to dig further into the past, I’m sure we’ll find earlier references to this simple idea of encoding binary data in text form.

Now, in 2014, we finally have JEP 135 as a part of the JavaSE8, and thus (you wouldn’t believe it): java.util.Base64.

Off to the trash can with all of these libraries!

… gee, it seems like everyone and their dog worked around this limitation, prior to the JDK 8…

More?

Provide your suggestions in the comments! We’re curious to hear your thoughts (with examples!)

Conclusion

As any Java major release, there is a lot of new stuff that we have to learn, and that allows us to remove third-party libraries. This is great, because many good concepts have been consolidated into the JDK, available on every JVM without external dependencies.

Disclaimer: Not everything in this article was meant seriously. Many people have created great pieces of work in the past. They have been very useful, even if they are somewhat deprecated now. Keep innovating, guys! :-)

Want to delve more into the many new things Java 8 offers? Go have a look over at the Baeldung blog, where this excellent list of Java 8 resources is featured:

http://www.baeldung.com/java8

… and stay tuned for our next Java 8 Friday blog post, next week!

The Lame Side of Java’s Backwards-Compatibility

Java is a very backwards-compatible language. Very as in very very very. It is so backwards compatible, we still have tons of deprecated code that was deprecated in the JDK 1.1. For example, most of the java.util.Date and java.util.Calendar API. Some may argue that it would’ve been easier to deprecate the classes altogether…

But things don’t get better as we’re approaching Java 8. Please, observe with me with a mixture of intrigue and disgust what is going to be added to the JDBC 4.2 specs:

“large”. As in “We should’ve made that a long instead of an int from the very beginning”. Luckily, Java 8 also introduces defender methods, such that the additions were done backwards-compatibly.

I wonder how many other places in the JDK should now have duplicate methods using the “large” term, because in the beginning, people chose int over long, when most processors were still 32bit, and it really did make a difference.

Also, I wonder what’ll happen when we run out of 64bit space in the year 2139, as mankind will reach the outer skirts of milky way. In order to be able to write the occasional planet-migration script, we’ll have to add things like executeHugeUpdate() to the JDBC specs in Java 11 – if we’re optimistic that Java 11 will be shipped by then ;-)

For more info, you can see the up-to-date OpenJDK source code here:
http://hg.openjdk.java.net/lambda/lambda/jdk/file/tip/src/share/classes/java/sql/Statement.java