Slippery Slope of Java 8
With the advent of Java 8, Closures and Lambdas have been added to the language. While this makes it (a lot) easier to create certain constructions (I won’t get into the details here), there are a number of downsides to it. Let’s start with one of most obvious disadvantages, namely that a lot of complexity is delegated away from the developer, which makes the application flow less transparent. Consequently, the development itself becomes a lot more difficult for, for example, less experienced developers. This is a big problem, because it reduces the pool of developers capable of creating the software, while there appears to be a large shortage of Java developers in the first place!
Looking back, the really big problems started with Java 5 and the introduction of Generics. When Generics were introduced in 2004, Sun decided it would be very important to maintain backwards compatibility, resulting in the implementation becoming a bit of a hack of the existing JVM. This is what they did in a nutshell:
They added an extra attribute to a class definition that would contain the generic expression verbatim, ie. not compiled,
This attribute had it’s own syntax and would rely on individual compilers and runtimes to say something about what was happening,
The use of these generic parameters was completely optional, not declaring the parameter would simply instruct the compiler to more or less ignore it,
Then at compile time, the compiler would check the types and associate the generic parameter to the specific instance it was evaluating,
This meant that at runtime, two objects with the same raw class but a different generic parameter have the same type!
To then enforce some kind of type checking, bridge methods would be introduced that would do the type checking for the real methods (void someMethod(Object someParameter) ->
One of the more significant gaping holes here were the fact that generic parameters could come from anywhere and go to anywhere. Which meant that to resolve a generic parameter, this would sometimes have to be inferred. Requiring even more extra bits and pieces and tricks to make the inference work. At first, this lead to different results in the Sun compiler versus the Eclipse compiler. The Eclipse compiler was a bit smarter about inference than the Sun compiler was. As such, a developer would do something in Eclipse, compile and run the file, then commit (and push) his work to a central repository, the continuous integration environment would pick it up and fail miserably on some type error.
10 years later Java 8 was released. Java 7 came out a little earlier and fixed some of our inference problems. But now we have closures and lambdas. So now we have these extra tricks to do cool stuff by doing creative things with methods. What we didn’t realize at the time was the implications this would have on the generics implementation of the JVM. So the bridge methods that did the type checking still exist, but they just grew an evil twin. That is, when using Generics in closures, suddenly we were in need of a much more powerful bridge method, namely a dynamic bridge method. So invokedynamic was used to resolve this issue. But invokedynamic does runtime type checking! Imagine you had a problem with inference that the Oracle JVM couldn’t resolve, but Eclipse could. As a developer, you’re none the wiser, all looks good, software compiles, then you run.
Going back a bit, in the case of Java 5, when inference failed, it would cause a compile time exception. But the code was logical and good, the compiler just didn’t get it. So if you used the Ecilpse compiler to create a package and went back to the Sun/Oracle runtime. You’d be fine, all the issues were caught at compile time, not runtime. Then comes Java 8, doing these checks at runtime. So if you had a hidden Generics problem that went fine with the Eclipse compiler and another JVM. You then go back to the Oracle runtime and run the same logical code, you’d be facing a very nasty bytecode dump in a stacktrace.
The fact that this may happen is extremely damaging to the quality of an application. There are and have always been subtle issues between JVMs that would cause issues. For instance poor auto-boxing issues. But there would always be something to warn you and at face value, you would always be able to see what was going on. Now the situation in different though, as it is a lurking issue that is moreover a pain to resolve and there is no obvious way to even see what’s going wrong in the code.
For those curious to know what causes most issues, both in Java 5 and Java 8, the large weak spot with the inference we’ve discussed so far are the wildcard generic bounds. Sun and Oracle compilers have traditionally had a lot of trouble with any kind of bounds in type inference.