The Dark Side Of Lambda Expressions in Java 8

By Tal Weiss —  March 25, 2014 — 33 Comments

blog lambada 2 The Dark Side Of Lambda Expressions in Java 8

This post may not make me any new friends. Oh well, I was never really popular at school anyway. But let’s get to the point. Java 8’s biggest feature in terms of the language is undoubtedly Lambda expressions. It’s been a flagship feature for functional languages such as Scala and Clojure for a few years, and now Java has finally joined in.

The second biggest feature (depending of course on who you ask) is Nashorn – the new JVM JavaScript engine that’s supposed to bring Java up to par with other JS engines such as V8 and its node.js container.

But these new features have a dark side to them.

I’ll explain. The Java platform is built out of two main components. The JRE, which JIT compiles and executes bytecode, and the JDK which contains dev tools and the javac source compiler. These two components are fairly (but not fully) decoupled, which is what enables folks to write their own JVM languages, with Scala rising to prominence in the last few years. And therein lies some of the problem.

The JVM was built to be language agnostic in the sense that it can execute code written in any language, as long as it can be translated into bytecode. The bytecode specification itself is fully OO, and was designed to closely match the Java language. That means that bytecode compiled from Java source will pretty much resemble it structurally.

But the farther away you get from Java – the more that distance grows. When you look at Scala which is a functional language, the distance between the source code and the executed bytecode is pretty big. Large amounts of synthetic classes, methods and variables are added by the compiler to get the JVM to execute the semantics and flow controls required by the language.

When you look at fully dynamic languages such as JavaScript, that distance becomes huge.

And now with Java 8, this is beginning to creep into Java as well.

So why should I care?

I wish this could be a theoretical discussion, that while interesting, has no practical implication on our everyday work. Unfortunately it does, and in a very big way. With the push to add new elements into Java, the distance between your code and the runtime grows, which means that what you’re writing and what you’re debugging will be two different things.

To see how let’s (re)visit the example below.


Java 6 & 7

This is the traditional method by which we would iterate over a list of strings to map their lengths.


// simple check against empty strings
public static int check(String s) {
if (s.equals("")) {
throw new IllegalArgumentException();
}
return s.length();
}

//map names to lengths

List lengths = new ArrayList();

for (String name : Arrays.asList(args)) {
lengths.add(check(name));
}

This will throw an exception if an empty string is passed. The stack trace will look like –


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

Here we see a 1:1 correlation between the stack trace we see and the code we wrote, which makes debugging this call stack pretty straightforward. This is what most Java devs are used to.

Now let’s look at Scala and Java 8.


Scala

Let’s look at the same code in Scala. Here we’ve got two big changes. The first is the use of a Lambda expression to map the lengths, and the second is that the iteration is carried out by the framework (i.e. internal iteration).


val lengths = names.map(name => check(name.length))

Here we really start to notice the difference between how the code you wrote looks, and how the JVM (and you) will see it at run time. If an exception is thrown, the call stack is an order of magnitude longer, and much harder to understand.


at Main$.check(Main.scala:6)
at Main$$anonfun$1.apply(Main.scala:12)
at Main$$anonfun$1.apply(Main.scala:12)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.immutable.List.foreach(List.scala:318)
at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
at scala.collection.AbstractTraversable.map(Traversable.scala:105)
at Main$delayedInit$body.apply(Main.scala:12)
at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
at scala.App$$anonfun$main$1.apply(App.scala:71)
at scala.App$$anonfun$main$1.apply(App.scala:71)
at scala.collection.immutable.List.foreach(List.scala:318)
at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:32)
at scala.App$class.main(App.scala:71)
at Main$.main(Main.scala:1)
at Main.main(Main.scala)

* Remember, this example is very simple. With real-world nested Lambdas and complex structures you’ll be looking at much longer synthetic call stacks, from which you’ll need to understand what happened.

This has long been an issue with Scala, and one of the reasons we built the Scala Stackifier.


And now in Java 8

Up until now Java developers were pretty immune to this. This will change as Lambda expressions become an integral part of Java. Let’s look at the corresponding Java 8 code, and the resulting call stack.


Stream lengths = names.stream().map(name -> check(name));

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)

This is becoming pretty similar to Scala. We’re paying the price for shorter, more concise code with more complex debugging, and longer synthetic call stacks.

The reason is that while javac has been extended to support Lambda functions, the JVM still remains oblivious to them. This has been a design decision by the Java folks in order to to keep the JVM operating at a lower-level, and without introducing new elements into its specification.

And while you can debate the merits of this decision, it means that as Java developers the cost figuring out these call stacks when we get a ticket now sadly lies on our shoulders, whether we want to or not.


JavaScript in Java 8

Java 8 introduces a brand new JavaScript compiler. Now we can finally integrate Java + JS in an efficient and straightforward manner. However, nowhere is the dissonance between the code we write and the code we debug bigger than here.

Here’s the same function in Nashorn –


ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");

String js = "var map = Array.prototype.map \n";
js += "var a = map.call(names, function(name) { return Java.type(\"LmbdaMain\").check(name) }) \n";
js += "print(a)";
engine.eval(js);

In this case the bytecode code is dynamically generated at runtime using a nested tree of Lambda expressions. There is very little correlation between our source code, and the resulting bytecode executed by the JVM. The call stack is now two orders of magnitude longer. In the poignant words of Mr.T – I pity the fools who will need to debug the call stack you’ll be getting here.

Questions, comments? (assuming you can scroll all the way below this call stack). Let me know in the comments section.

LmbdaMain [Java Application]
LmbdaMain at localhost:51287
Thread [main] (Suspended (breakpoint at line 16 in LmbdaMain))
LmbdaMain.wrap(String) line: 16
1525037790.invokeStatic_L_I(Object, Object) line: not available
1150538133.invokeSpecial_LL_I(Object, Object, Object) line: not available
538592647.invoke_LL_I(MethodHandle, Object[]) line: not available
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
2150540.interpret_I(MethodHandle, Object, Object) line: not available
538592647.invoke_LL_I(MethodHandle, Object[]) line: not available
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
92150540.interpret_I(MethodHandle, Object, Object) line: not available
38592647.invoke_LL_I(MethodHandle, Object[]) line: not available
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
731260860.interpret_L(MethodHandle, Object, Object) line: not available
LambdaForm$NamedFunction.invoke_LL_L(MethodHandle, Object[]) line: 1108
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
2619171.interpret_L(MethodHandle, Object, Object, Object) line: not available
1597655940.invokeSpecial_LLLL_L(Object, Object, Object, Object, Object) line: not available
LambdaForm$NamedFunction.invoke_LLLL_L(MethodHandle, Object[]) line: 1118
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
2619171.interpret_L(MethodHandle, Object, Object, Object) line: not available
1353530305.linkToCallSite(Object, Object, Object, Object) line: not available
Script$\^eval\_._L3(ScriptFunction, Object, Object) line: 3
1596000437.invokeStatic_LLL_L(Object, Object, Object, Object) line: not available
1597655940.invokeSpecial_LLLL_L(Object, Object, Object, Object, Object) line: not available
LambdaForm$NamedFunction.invoke_LLLL_L(MethodHandle, Object[]) line: 1118
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
484673893.interpret_L(MethodHandle, Object, Object, Object, Object, Object) line: not available
LambdaForm$NamedFunction.invoke_LLLLL_L(MethodHandle, Object[]) line: 1123
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
282496973.interpret_L(MethodHandle, Object, Object, Object, long, Object) line: not available
93508253.invokeSpecial_LLLLJL_L(Object, Object, Object, Object, Object, long, Object) line: not available
1850777594.invoke_LLLLJL_L(MethodHandle, Object[]) line: not available
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
282496973.interpret_L(MethodHandle, Object, Object, Object, long, Object) line: not available
293508253.invokeSpecial_LLLLJL_L(Object, Object, Object, Object, Object, long, Object) line: not available
1850777594.invoke_LLLLJL_L(MethodHandle, Object[]) line: not available
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
1840903588.interpret_L(MethodHandle, Object, Object, Object, Object, long, Object) line: not available
2063763486.reinvoke(Object, Object, Object, Object, Object, long, Object) line: not available
850777594.invoke_LLLLJL_L(MethodHandle, Object[]) line: not available
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
82496973.interpret_L(MethodHandle, Object, Object, Object, long, Object) line: not available
220309324.invokeExact_MT(Object, Object, Object, Object, long, Object, Object) line: not available
NativeArray$10.forEach(Object, long) line: 1304
NativeArray$10(IteratorAction).apply() line: 124
NativeArray.map(Object, Object, Object) line: 1315
1596000437.invokeStatic_LLL_L(Object, Object, Object, Object) line: not available
504858437.invokeExact_MT(Object, Object, Object, Object, Object) line: not available
FinalScriptFunctionData(ScriptFunctionData).invoke(ScriptFunction, Object, Object...) line: 522
ScriptFunctionImpl(ScriptFunction).invoke(Object, Object...) line: 207
ScriptRuntime.apply(ScriptFunction, Object, Object...) line: 378
NativeFunction.call(Object, Object...) line: 161
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
1740189450.invokeSpecial_LLL_L(Object, Object, Object, Object) line: not available
LambdaForm$NamedFunction.invoke_LLL_L(MethodHandle, Object[]) line: 1113
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
2619171.interpret_L(MethodHandle, Object, Object, Object) line: not available
LambdaForm$NamedFunction.invoke_LLL_L(MethodHandle, Object[]) line: 1113
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
323326911.interpret_L(MethodHandle, Object, Object, Object, Object) line: not available
LambdaForm$NamedFunction.invoke_LLLL_L(MethodHandle, Object[]) line: 1118
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
323326911.interpret_L(MethodHandle, Object, Object, Object, Object) line: not available
263793464.invokeSpecial_LLLLL_L(Object, Object, Object, Object, Object, Object) line: not available
LambdaForm$NamedFunction.invoke_LLLLL_L(MethodHandle, Object[]) line: 1123
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
1484673893.interpret_L(MethodHandle, Object, Object, Object, Object, Object) line: not available
587003819.invokeSpecial_LLLLLL_L(Object, Object, Object, Object, Object, Object, Object) line: not available
811301908.invoke_LLLLLL_L(MethodHandle, Object[]) line: not available
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
484673893.interpret_L(MethodHandle, Object, Object, Object, Object, Object) line: not available
LambdaForm$NamedFunction.invoke_LLLLL_L(MethodHandle, Object[]) line: 1123
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
LambdaForm$NamedFunction.invokeWithArguments(Object...) line: 1147
LambdaForm.interpretName(LambdaForm$Name, Object[]) line: 625
LambdaForm.interpretWithArguments(Object...) line: 604
323326911.interpret_L(MethodHandle, Object, Object, Object, Object) line: not available
2129144075.linkToCallSite(Object, Object, Object, Object, Object) line: not available
Script$\^eval\_.runScript(ScriptFunction, Object) line: 3
1076496284.invokeStatic_LL_L(Object, Object, Object) line: not available
1709804316.invokeExact_MT(Object, Object, Object, Object) line: not available
FinalScriptFunctionData(ScriptFunctionData).invoke(ScriptFunction, Object, Object...) line: 498
ScriptFunctionImpl(ScriptFunction).invoke(Object, Object...) line: 207
ScriptRuntime.apply(ScriptFunction, Object, Object...) line: 378
NashornScriptEngine.evalImpl(ScriptFunction, ScriptContext, ScriptObject) line: 544
NashornScriptEngine.evalImpl(ScriptFunction, ScriptContext) line: 526
NashornScriptEngine.evalImpl(Source, ScriptContext) line: 522
NashornScriptEngine.eval(String, ScriptContext) line: 193
NashornScriptEngine(AbstractScriptEngine).eval(String) line: 264
LmbdaMain.main(String[]) line: 44

takipi logo 03 The Dark Side Of Lambda Expressions in Java 8

Java Developers – Know when and why production code breaks – Start Your Free Trial

Duke 8 copy 300x196 The Dark Side Of Lambda Expressions in Java 8

Already using Java 8? Try Takipi for Java 8 

Tal Weiss

Posts Twitter

Tal is the CEO of Takipi. Tal has been designing scalable, real-time Java and C++ applications for the past 15 years. He still enjoys analyzing a good bug though, and instrumenting code. In his free time Tal plays Jazz drums.
  • Ben Burton

    Great post, but it’s hard to correlate the line numbers from the stack traces to the source when you’ve only included parts of the files. The first example’s stack trace references LmbdaMain.java:19, which presumably corresponds to line 4 in the source provided.

    • http://www.takipi.com/ Tal Weiss

      Hi Ben,

      Thanks for the comment. The surrounding code is a simple main[] stub. I left it out for brevity.

  • javawerks

    Many have noted this issue with both Scala and Java 8. I will gladly pay the price, though. And I would argue the Java 8 stack trace is much easier to read than the Scala stack trace. Javascript is just insane.:) So, in your opinion, what does the future hold for the JVM, as languages evolve?

    • http://www.takipi.com/ Tal Weiss

      My sense is that the more broader and higher level JVM languages become (is a strong trend in programming over the last few years) the JVM itself will also have to adapt itself to support some higher level structures and metadata to alleviate a situation like in C++ where the distance between the source code and the machine code that runs us just so big. You can even see some work that’s been done in Java 8 to bring higher level metadata closer to the framework level – http://openjdk.java.net/projects/jdk8/features#118

      • javawerks

        That makes sense. So it sounds as though the JVM can be adapted to grow with higher level languages, allowing it to stay in service for years to come ( no JVM rewrite required ).

  • Logician

    I don’t see a huge issue with this, as in the little debugging I have to do, I usually only look at the top, and sometimes the bottom, of the call stack. I don’t care what’s in-between unless I’m digging through abstraction layers looking for a bug that seems to be a hidden implementation detail.

    The bottom of the call stack is where the lambda is, and the top of the call stack is where the exception was thrown. In this situation, that’s all I’d need to debug the problem. The rest is just fluff.

  • R Samuel Klatchko

    For the Java 8 example, that appears to be an issue with the streaming framework and not the lambda expression. While it looks to me like the lambda expression adds two extra levels to the call stack, the bulk of the call stack is from the streaming framework.

    • http://www.takipi.com/ Tal Weiss

      Hey Samuel,

      You’re right in that most of the frames are coming from the framework, but keep in mind that Lambda expressions are almost exclusively used within the context of a data processing framework, especially when dealing with internal iteration as with map / reduce / filter functions, which are the a primary use-case of Lambdas.

      • anthavio

        I strongly disagree

        • Zippy

          I strongly agree. :)

      • Ladicek

        I immediately came with the same objection as Samuel when reading this article: the lambda only adds 1 frame to the stack trace, the rest is the incredibly powerful “stream” framework that got added to the collection framework in Java 8.

        What happens here and what you are showing in this blog post is simple: your code became less complex (a lot) while the standard library became more complex (also a lot). Writing code became less complex (a little) while debugging code became more complex (I claim that also a little). The law of conservation of software complexity in practice :-)

      • João Bispo

        Since I’ve started using Java 8, most of my lambdas are related with creation of “anonymous classes”, and I’ve been using them a lot. Not so much on data processing, although it should also be a common case.

  • http://www.jooq.org Lukas Eder

    I don’t really understand the fear of long stack traces. Yes, they’re harder to read but isn’t the main issue here that Takipi might simply not be ready for helping solve this issue for users?

    A small piece of advice: Instead of ranting, why not provide a solution by simplifying things for your users e.g. by collapsing “useless” stack trace elements? Great business opportunity for you guys! :-)

    • http://www.takipi.com/ Tal Weiss

      Hey Lukas,

      One step ahead of you :)http://stackifier.takipi.com/scala.html

      • http://www.jooq.org Lukas Eder

        I should have known better! That is really awesome! Do you have more, similar examples (also for Java, Java 8, Java/Spring/AOP/Hibernate)? I’ll feature this on the jOOQ blog within the next two weeks (syndicated on DZone, JCG, Tech.Pro)

        • http://www.takipi.com/ Tal Weiss

          Cool! here’s the Java version as well – stackifier.takipi.com

          • http://www.jooq.org Lukas Eder

            Thanks. That’ll do for a short post.

            I really like your brand, btw. The “personal monsters” per employee are a nice idea!

          • http://www.takipi.com/ Tal Weiss

            Thanks! all the credit goes to @IrisShoor

  • http://logicopolis.com Sven

    I pity the foo that has to deal with debug and stack traces on code that uses lambda’s that themselves use lambda’s that themselves use lambda’s… yeck!

  • gacl

    Higher order function usage does have a cost of reduced stack trace simplicity. The OP is correct but this is still a good trade. This also really isn’t a flaw or limitation in the JVM, it’s more the nature of higher order functions.

  • Simon Ritter

    As someone else pointed out, the bulk of the stack trace is generated by the Streams API, not the Lambda expression. Lambda expressions are not, as many people seem to assume, syntactic sugar for anonymous inner classes. They are implemented using the invokedynamic bytecode, which was introduced in Java SE 7 specifically to make it easier to implement compilers for dynamically typed languages that generate efficient bytecodes. You make a good point about the challenges of debugging in this case, though. Also, you are a bit confused about the terms JDK, JRE and JVM. The JRE is the Java Runtime Environment, and provides the JVM, class libraries and some other runtime tools (like jjs for Nashorn). The JRE does not execute bytecodes, this is job of the JVM (Java Virtual Machine). The JDK (Java Development Kit) contains the JRE plus other tools required to develop Java applications (like javac, javah, etc).

    • http://www.takipi.com/ Tal Weiss

      Hi Simon,

      Thanks for the great comment. I’m personally fairly clear on the diffs between the JRE / JDK, but if I somehow misrepresented those in my writing, I really appreciate you adding the clarification. Big thanks.

      Re Lambdas, we’ve actually done a post on both how Scala and Java 8 implemented Lambda expressions at the compiler level (each in its own way), including Java 8’s use of the invokeDynamic instruction. See here – http://www.takipiblog.com/2014/01/16/compiling-lambda-expressions-scala-vs-java-8/

  • La VloZ

    But i think, you don’t need to debug a lambda expression because it’s very short especially for streams methods, like filter or map :), and if you want to write a long code u probably don’t use lambda expression for it?? Nah?? :)

  • kishore.k Nayar

    Great post !

    • http://www.takipi.com/ Tal Weiss

      Thanks Kishore!

  • kishore.k Nayar

    Great Post !

  • Attila Szegedi

    The long stack trace from Nashorn is only visible if you run the JVM with -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames. You need to go to extra trouble to do that, and people ordinarily won’t see it. If you just run “java” without those -XX options, you get 15 lines in the stack trace, not 134 as in your post.

    Also, what you see in the diagnostic trace are LambdaForm methods – lambda forms are actually not related to the Java lambda feature, it’s just a naming coincidence for the implementation of the MethodHandle plumbing classes. They are actually used even in recent Java 7 updates for implementing method handles, and Java 7 doesn’t have lambdas.

    • http://www.takipi.com/ Tal Weiss

      Hi Attila,

      Thanks for the comment. You’re right in that this is the full call-stack representation. The downside of going with the shorter one is that even there you don’t actually get the actual JavaScript call stack (at least from my experiments) ,which makes it harder to debug IMO. So for example, if a JS function foo() calls boo(), you don’t see that in the cal stack (only calls to the Nashorn engine, and calls which map into Java methods). This I think makes debugging a script harder, especially if its not a trivial one.

      Re lambdas, I didn’t want to allude that the method handle mechanism used by J7 invokeDynamic is the same J8 Lambda syntax (which only really exists at the javac level). Even so, if I was unclear in my writing I apologize and really appreciate the clarification.

  • http://garytrakhman.com Gary Trakhman

    Just looked at a 134 line clojure stack trace, I’m totally used to this by now, so this post is amusing :-). It’s easy to filter signal from noise with enough experience and knowledge of implementation.

  • An0nym0usC0ward

    I’ll take the convenience of being able to use functional programming where appropriate over the convenience of debugging any day. Debugging sucks, unit testing rocks.

    If your code is properly unit-tested, you shouldn’t spend much time in the debugger.

    There’s also tool support to deal with the problem. IIRC, the debugger in Eclipse allows you to tell it what classes/methods not to show in stack traces.

  • Tiberiu Tofan

    Great advertorial :)

  • DutchMark

    If you think that type of issue is new to Java 8 then you haven’t been doing any AspectJ development, which is a good decade old. :) Developers of debuggers will have to step up to make our life a little easier.

  • Stephan Stross

    Oh god. That Scala stack trace… Can you get PTSD from C++ templates? Because that Scala stack trace was giving me some crazy flashbacks…
    EDIT: NEVERMIND. The JS one is INFINITELY worse…