Chen is a Co-Founter at Takipi and among other things, responsible for the company's cloud architecture and stack discovery. Chen has posted 18 posts at DZone. You can read more from them at their website. View Full User Profile

6 Reasons Not to Switch to Java 8 Just Yet

07.11.2014
| 6899 views |
  • submit to reddit


Java 8 is awesome. Period. But… after we had the chance to have fun and play around with it, the time has come to quit avoiding the grain of salt. All good things come with a price and in this post I will share the main pain points of Java 8. Make sure you’re aware of these before upgrading and letting go of 7.

1. Parallel Streams can actually slow you down

Java 8 brings the promise of parallelism as one of the most anticipated new features. The .parallelStream() method implements this on collections and streams. It breaks them into subproblems which then run on separate threads for processing, these can go to different cores and then get combined when they’re done. This all happens under the hood using the fork/join framework. Ok, sounds cool, it must speed up operations on large data sets in multi-core environments, right?

No, it can actually make your code run slower if not used right. Some 15% slower on this benchmark we ran, but it could be even worse. Let’s say we’re already running multiple threads and we’re using .parallelStream() in some of them, adding more and more threads to the pool. This could easily turn into more than our cores could handle, and slow everything down due to increased context switching.

The slower benchmark, grouping a collection into different groups (prime / non-prime):

Map<Boolean, List<Integer>> groupByPrimary = numbers
.parallelStream().collect(Collectors.groupingBy(s -> Utility.isPrime(s)));

More slowdowns can occur for other reasons as well. Consider this, let’s say we have multiple tasks to complete and one of them takes much longer than the others for some reason. Breaking it down with .parallelStream() could actually delay the quicker tasks from being finished and the process as a whole. Check out this post by Lukas Krecan for more examples and code samples.

Diagnosis: Parallelism with all its benefits also brings in additional types of problems to consider. When already acting in a multi-threaded environment, keep this in mind and get yourself familiar with what’s going on behind the scenes.

2. The flip-side of Lambda Expressions

Lambdas. Oh, lambdas. We can do pretty much everything we already could without you, but you add so much grace and get rid of boilerplate code so it’s easy to fall in love. Let’s say I rise up in the morning and want to iterate over a list of world cup teams and map their lengths (Fun fact: it sums up to 254):

List lengths = new ArrayList();
 
for (String countries : Arrays.asList(args)) {
    lengths.add(check(country));
}

Now let’s get functional with a nice lambda:

Stream lengths = countries.stream().map(countries -> check(country));

Baam! That’s super. Although… while mostly seen as a positive thing, adding new elements like lambdas to Java pushes it further away from its original specification. The bytecode is fully OO and with lambdas in the game, the distance between the actual code and runtime grows larger. Read more about the dark side of lambda expression on this post by Tal Weiss.

On the bottom line this all means that what you’re writing and what you’re debugging are two different things. Stack traces grow larger and larger and make it harder to debug your code.

Something simple like adding an empty string to list turns this short stack trace:

at LmbdaMain.check(LmbdaMain.java:19)
at LmbdaMain.main(LmbdaMain.java:34)

Into this:

at LmbdaMain.check(LmbdaMain.java:19)
at LmbdaMain.lambda$0(LmbdaMain.java:37)
at LmbdaMain$$Lambda$1/821270929.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.LongPipeline.reduce(LongPipeline.java:438)
at java.util.stream.LongPipeline.sum(LongPipeline.java:396)
at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)
at LmbdaMain.main(LmbdaMain.java:39)

Another issue that lambdas raise has to do with overloading: since lambda arguments have to be cast into something when using them to call a method, and they can be cast to multiple types, it may cause ambiguous calls in some cases. Lukas Eder explains this with code samples right here.

Diagnosis: Just stay aware of this, the traces might be a pain from time to time, but it will not keep us away from them precious lambdas.

3. Default Methods are distracting

Default methods enable a default implementation of a function in the interface itself. This is definitely one of the coolest new features Java 8 brings to the table but it somewhat interferes with the way we used to do things. So why was this introduced anyway? And what not to do with it?

The main motivation behind Default Methods was that if at some point we need to add a method to an existing interface, we could do this without rewriting the implementation. Making it compatible with older versions. For example, take this piece of code from Oracle’s Java Tutorials where they add an ability to specify a timezone:

public interface TimeClient {
// ...
static public ZoneId getZoneId (String zoneString) {
try {
    return ZoneId.of(zoneString);
} catch (DateTimeException e) {
    System.err.println("Invalid time zone: " + zoneString +
    "; using default time zone instead.");
    return ZoneId.systemDefault();
    }
}
 
default public ZonedDateTime getZonedDateTime(String zoneString) {
    return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
    }
}

And that’s it, problem solved. Or is it? Default Methods mix up a bit the separation of interface and implementation. As if type hierarchies don’t tend to tangle up on their own, there’s this new creature now that we need to tame. Read more about it on Oleg Shelajev’s post on RebelLabs.

Diagnosis: When you hold a hammer everything looks like a nail, keep in mind to stick to their original use case, evolution of an existing interface when a refactor to introduce a new abstract class doesn’t make sense.

Moving on to some things that are either missing, still with us or not exactly there yet:

4. Wherefore art thou Jigsaw?

Project Jigsaw’s goal is to make Java modular and break the JRE to interoperable components. The motivation behind this first comes from a desire for a better, faster and stronger Java embedded. I’m trying to avoid mentioning the “Internet of Things”, but there I said it. Reduced JAR sizes, performance improvements and increased security are some more of the promises this ambitious project holds.

So where is it? Jigsaw entered Phase 2 just recently, passed the exploratory phase and is now switching gears to a production quality design and implementation, says Mark Reinhold, Oracle’s Chief Java Architect. The project was first planned to be completed in Java 8 and was deferred to Java 9, expected to be one of its flagship new features.

Diagnosis: If this is the main thing that you’re waiting for, Java 9 is due in 2016. In the meantime, take a closer look and maybe even get involved in the Jigsaw-dev mailing list.

5. Issues that are still around

Checked Exceptions
No one likes boilerplate code, that’s one of the reasons why lambdas got so popular. Thinking of boilerplate exceptions, regardless of whether or not you logically need to catch or have something to do with a checked exception, you still need to catch it. Even if it’s something that would never happen, like this exception that will never fire:

try {
    httpConn.setRequestMethod("GET");
} catch (ProtocolException pe) { /* Why don’t you call me anymore? */ }

Primitives
They are still here, and it’s a pain to use them right. The one thing that separates Java from being a pure Object Oriented language, criticized to have no significant performance hit for their removal. None of the new JVM languages has them, just saying.

Operator Overloading
James Gosling, the father of Java, once said in an interview “I left out operator overloading as a fairly personal choice because I had seen too many people abuse it in C++”. Kind of makes sense but there are lots of split opinions around this. Other JVM languages do offer this feature but on the other hand, it could result in code that looks like this:

javascriptEntryPoints <<= (sourceDirectory in Compile)(base =>
    ((base / "assets" ** "*.js") --- (base / "assets" ** "_*")).get
)

An actual line of code from the Scala Play Framework, ahm, I’m a bit dizzy now.

Diagnosis: Are these real problems anyway? We all have our quirks and these are some of Java’s. A surprise might happen in future versions and it will change, but backwards compatibility among other things is keeping them right here with us.

6. Functional Programming – not quite there yet

Functional programming has been possible with Java before, although it is pretty awkward. Java 8 improves on this with lambdas among other things. It’s most welcome but not as huge of a shift that was earlier portrayed. Definitely more elegant than in Java 7 but some bending over backwards is still needed to be truly functional.

One of the most fierce reviews on this matter comes from Pierre-yves Saumont where in a series of posts he takes a close look at the differences between functional programing paradigms and the way to implement them in Java.

So Java or Scala? The adoption of more functional modern paradigms in Java is a sign of approval for Scala who has been playing with lambdas for a while now. Lambdas do make a lot of noise, but there’s a lot more features like traits, lazy evaluation and immutables to name a few, that make quite a difference.

Diagnosis: Don’t be distracted by the lambdas, functional programming is still a hassle in Java 8.

Originally posted in Takipi's blog 

Published at DZone with permission of its author, Chen Harel.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Robert Saulnier replied on Mon, 2014/07/14 - 8:04am

None of the items listed are reasons not to use Java 8.

Oleksandr Alesinskyy replied on Wed, 2014/07/16 - 6:18am

 One major problem missed - non-reifilable generics. This was claimed to be an interim solution in Java 5 to be replaced in the next version - but is still there and seems to persist forever.

Mario Fusco replied on Wed, 2014/07/16 - 7:25am

I don't see any relationship between the title and the body of this article. Can you please point to a single good reason not to switch to Java 8?

Jeremy Pulcifer replied on Wed, 2014/07/16 - 12:06pm in response to: Mario Fusco

The subject should have been "Reasons to Not Blindly Use New Features Without Understanding the Impact". Which is, frankly, duh.

Tomm Carr replied on Wed, 2014/07/16 - 12:42pm

Primitives: None of the new JVM languages has them, just saying.
Yeah, right. A language without primitives is like having a universe without atoms. I've yet to see one. There may be some syntactical sugar to hide them, pretend they aren't there, but they are.

I was a programmer before Object Oriented was a meaningful phrase, much less a cliche. There were several aspects of Object Orientedness, but primitives (or rather lack of) was not one of them. This has been a fairly recent development and I completely fail to see what the noise is all about. Primitives are the basic building blocks of all objects.

What was a bit irritating when it was first released was not being able to use generics with primitives. But that is a deliberate design feature of Java generics, not a fault of primitives. And I've gotten used to it by now.

Can primitives be misused? Absolutely. But objects can be misused. What then?

I'm hoping that someone can give a clear, meaningful answer to: What is wrong with primitives? So far, all I've come across is rants and innuendos -- like this post. Nothing substantive. Or maybe someone could just explain how all this bad feelings for primitives got started in the first place. I seem to have missed that.

Serge Terekhov replied on Wed, 2014/07/16 - 3:49pm

Yes, Yes, Yes! Stay away from Java 8! Give your competitors a chance. :)

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.