Scala 3 Slowed Us Down?
Key topics
As developers debated whether Scala 3's performance regression was a one-off issue or a broader concern, a consensus emerged: robust automated performance testing and insightful profiling tools are crucial for catching such slowdowns. Commenters enthusiastically shared their go-to tools, including JMH for microbenchmarks, async-profiler, and Java Mission Control for production system analysis. The discussion highlighted the importance of nuanced benchmarking setups and leveraging hardware performance counters to gain deeper insights. With the JVM ecosystem constantly evolving, this conversation feels particularly timely and relevant.
Snapshot generated from the HN discussion
Discussion Activity
Very active discussionFirst comment
1h
Peak period
121
Day 1
Avg / period
22.9
Based on 160 loaded comments
Key moments
- 01Story posted
Dec 7, 2025 at 10:08 AM EST
26 days ago
Step 01 - 02First comment
Dec 7, 2025 at 11:15 AM EST
1h after posting
Step 02 - 03Peak activity
121 comments in Day 1
Hottest window of the conversation
Step 03 - 04Latest activity
Dec 16, 2025 at 11:59 PM EST
16 days ago
Step 04
Generating AI Summary...
Analyzing up to 500 comments to identify key contributors and discussion patterns
Want the full context?
Jump to the original sources
Read the primary article or dive into the live Hacker News thread when you're ready.
Then we do benchmarking of the whole Java app in the container running async-profiler into pyroscope. We created a test harness for this that spins up and mocks any dependencies based on api subscription data and contracts and simulates performance.
This whole mechanism is generalised and only requires teams that create individual apps to work with contract driven testing for the test harness to function. During and after a benchmark we also verify whether other non functionals still work as required, i.e. whether tracing is still linked to the right requests etc. This works for almost any language that we use.
For OOME problems I use a heap dump and eclipse memory analysis tool.
For microbenchmarks, I use JMH. But I tend to try and avoid doing those.
We have continous benchmarking of one of our tools, it's written in C++, and to get "same" results everytime we launch it on the same machine. This is far from ideal, but otherwise there be either noisy neighbours, pesky host (if it's vm), etc. etc.
One idea that we thought was what if we can run the same test on the same machine several times, and check older/newer code (or ideally through switches), and this could work for some codepaths, but not for really continous checkins.
Just wondering what folks do. I can assume what, but there is always something hidden, not well known.
In addition you can look at total cpu seconds used, memory allocation on kernel level, and specifically for the jvm at the GC metrics and allocation rate. If these numbers change significantly then you know you need to have a look.
We do run this benchmark comparison in most nightly builds and find regressions this way.
My first question was: why?
It also looks like it has some improvements for dealing with `null` from Java code. (When I last used it I rarely had to deal with null (mostly dealt with Nil, None, Nothing, and Unit) but I guess NPEs are still possible and the new system can help catch them.)
If anything is slowly down Scala 3 is that, including the tooling ecosystem that needs to be updated to deal with it.
Scala 3's optionally allows indentation based, brace-less syntax. Much closer to the ML family or Python, depending on how you look at it. It does indeed look better, but brings its share of issues.[1] Worse, a lot of people in the community, whether they like it or not, think this was an unnecessary distraction on top of the challenges for the entire ecosystem (libraries, tooling, ...) after Scala 3.0 was released.
- [1] https://alexn.org/blog/2025/10/26/scala-3-no-indent/
[1] https://coffeescript.org/#introduction
Also the silent majority thinks that the people who still lament over that change are just a very vocal minority.
Almost all Scala 3 code uses the new syntax, no matter how loud a few people cry. Similar situation to systemd on Linux…
Nothing to do with Haskell, even if it is also white space significant.
The Eclipse plugin isn't, and none of the newer IDE integrations is reliable.
The most reliable Scala IDE is currently Metals (in VSCode, but other editors work, too). Metals uses directly the compiler for all code intelligence so it's as reliable as the compiler itself.
https://scalameta.org/metals/
https://scalameta.org/metals/docs/#editor-support
For Scala 2, yes, or there was the last I looked. Still the best Scala development experience by some margin, sadly.
> Metals uses directly the compiler for all code intelligence so it's as reliable as the compiler itself.
Not my experience; maybe it theoretically should be but the integration/bridging piece is still flaky.
I can't find it.
Could you link to that "best Scala development experience by some margin"?
All I know is that the Eclipse plugin is dead since about one decade. But maybe I just missed something.
> the integration/bridging piece is still flaky
What concrete issues do you have?
I'm using Metals on a daily basis and don't know about any such problems.
Could it be that the last time you've seen Scala (if you actually ever seen it at all) was about 10 years ago?
The discussions here on HN regarding Scala seem always massively dishonest, with a lot of people always spreading outright FUD for some reason I don't understand…
Now we x2 by having the curly brace syntax and the indent syntax.
At the time Scala was on upswing because it had Spark as its killer app. It would have been a good time for the Scala maintainers to switch modes - from using Scala as a testbed for interesting programming-language theories and extensions to providing a usable platform as a general commercially usable programming language.
It missed the boat I feel. The window has passed (Spark moved to Python and Kotlin took over as the "modern" JVM language) and Scala is back to being an academic curiosity. But maybe the language curators never saw expanding mainstream usage as a goal.
Put another way: Java only has access to a subset of the ecosystem
Almost all of the backend libraries I use are Java libs. Some of them have additional Kotlin extension libs that add syntax sugar for more idiomatic code.
For what it's worth, Spring has first tier Kotlin support, I haven't noticed this bias.
And yes, "you get what you pay for" is part of this.
Because in 5-10 years you'll have a Java project that people can still maintain as if it's any other Java project. If you pick Kotlin, that might at that point no longer be a popular language in whatever niche you are in. What used to be the cool Kotlin project is now seen as a burden. See: Groovy, Clojure, Scala. Of course, I recognize that not all projects work on these kinds of timelines, but many do, including most things that I work on.
https://medium.com/@ankushroy7/tiobe-is-trash-why-it-gets-mo...
Tiobe bullshit unfolding looks like:
https://news.ycombinator.com/item?id=24997496
Kotlin is Google's C#, with Android being Google's .NET, after Google being sued by coming up with Google's J++, Android Java dialect.
Since Google wasn't able to come up with a replacement themselves, Fuchsia/Dart lost the internal politics, they adopted the language of the JetBrains, thanks to internal JetBrains advocates.
The Oracle v Google was specifically over copyright infringement concerning the Java APIs used in Android's original implementation (Dalvik/ART), not about creating a "J++" dialect.
Android never ran a JVM on mobile because it cannot be optimized for resource constrained devices a solution like DalvikVM was necessary. If you want to level critiques about creating fragmented dialects of Java I would recommend starting with J2ME. The only nice thing I can say about J2ME is at least it died.
The Android ecosystem was far too mature for Fuchsia/Dart to be successful without a very compelling interop story that was never produced.
As a technology Kotlin met Android's platform and community needs. Advocacy and politicking played a minimal, if any, role.
Nokia and Sony Ericsson were using J2ME perfectly fine, as did Blackberry. I should know ad ex-Nokian.
Kotlin met nothing, it was pushed by Kotlin heads working on Android Studio, telling lies comparing Kotlin to Java 7, instead of Java was already offering at the time.
To this day they never do Kotlin vs Java samples, where modern Java is used, rather the version that bests fits their purpose to sell why Kotlin.
Fragmentation, what a joke, the fragmentation got so bad in Android, that JetPack libraries, previously Android X, exist to work around the fragmentation and lack of OEM updates.
Gosling said it better, regarding Google's "good" intentions
https://www.youtube.com/watch?v=ZYw3X4RZv6Y&feature=youtu.be...
If Sun was offering some technically relevant foundation for the smartphone era, it would have been able to actually have some adoption. They were starting from a leading position (obviously - see blackberry or Nokia), and in the space of 3 to 4 years they completely disappeared.
So Google?
(alphabet)
I've written multiple production services in Kotlin Spring Boot. Now, we're building a new system and using Java 21 (25 soon).
Why? Kotlin the language is great, but there are corresponding tradeoffs in interop. Meanwhile, Java the language has improved to the point that it's good enough, and Java feels like it's headed in the right direction. In my opinion, AI models are better at Java than Kotlin. If you prefer a weaker claim, the models are trained on more Java code than Kotlin code.
Finally, from an enterprise perspective, it is a safer long-term investment for a Java shop to own an application written in Java rather than in Kotlin.
Personally, I'm extremely glad to not have had to write .toStream().map(...).collect(Collectors.list()) or whatever in years for what could be a map. Similar with async code and exception handling.
For me one of the main advantages of Kotlin is that is decreases verbosity so much that the interesting business logic is actually much easier to follow. Even if you disregard all the things it has Java doesn't the syntax is just so much better.
[1] https://paulgraham.com/avg.html
This is true, but needs more context. Java 8 added Stream API, which (at this time) was a fantastic breath of fresh air. However, the whole thing felt overengineered at many points, aka - it made complex things possible (collector chaining is admittedly cool, parallel streams are useful for quick-and-dirty data processing), but simple everyday things cumbersome. I cannot emphasize how tiring it was to have to write this useless bolierplate
for 1000th time, knowing that is what users need 99.99999% of the time.Of course you can write a generic version of `mkString` (as this method is called in Scala), so it's also just one method no matter the container count.
The Python weirdness is actually a direct result from the language lacking generics…
For example I'm lost which abstract class to inherit in Scala to obtain mkString for my custom container.
Of course such non-discoverable and unintuitive design gets ignored everywhere!
We just established that even in Python the correct way to do it would be
but for that Python would need generic iterators on the language level…> For example I'm lost which abstract class to inherit in Scala to obtain mkString for my custom container.
So you're saying you've been able to implement custom Scala collection types, which is regarded some of the more difficult stuff one could possibly do, but you don't know how to implement an Iterator for your custom collection—as this is all needed for mkString to work? BTW, how did you implement the collection type at all without implementing Iterator for it? Your collection is not Iterable?
TBH, this does not sound very realistic. This sounds more like typical HN Scala FUD. People throwing around some vague, most of the time outright made up issues they've heard about somewhere about 10 - 15 years ago.
The semantically correct one is "Container[T : Printable].join(): String"; T must implement the Printable concept, but Python lacks concepts (or type-classes as these are alternatively called).
But this all is irrelevant as this is anyway not the signature of `str.join()` in Python. It's `str.join(Iterable[str]) -> str` there. With sane design and syntax it would just become `Iterable[str].join(str)`.
The choice was Kotlin. Scala is too "powerful" and can be written in a style that is difficult for others, and Java too verbose.
Kotlin is instantly familiar to modern TypeScript/Swift/Rust etc devs.
The only negative in my mind has been IntelliJ being the only decent IDE, but even this has changed recently with Jetbrains releasing `kotlin-lsp` for VS Code
https://github.com/Kotlin/kotlin-lsp
https://business4s.org/scala-adoption-tracker/
The stuff currently cooking in Scala 3 will once more revolutionize the whole programming language landscape.
https://softwaremill.com/understanding-capture-checking-in-s...
https://www.youtube.com/watch?v=p-iWql7fVRg
Outside Android, I don't even care it exists.
If I remember correctly, latest InfoQ survey had it about 10% market share of JVM projects.
When inline is used on a parameter, it instructs the compiler to inline the expression at the call site. If the expression is substantial, this creates considerable work for the JIT compiler.
Requesting inlining at the compiler level (as opposed to letting the JIT handle it) is risky unless you can guarantee that a later compiler phase will simplify the inlined code.
There's an important behavioral difference between Scala 2 and 3: in 2, @inline was merely a suggestion to the compiler, whereas in 3, the compiler unconditionally applies the inline keyword. Consequently, directly replacing @inline with inline when migrating from 2 to 3 is a mistake.
In general it's a performance benefit and I never heard of performance problems like this. I wonder if combined with Scala's infamous macro system and libraries like quicklens it can generate huge expressions which create this problem.
They should have made use of JVM bytecodes that allow to optimize lambdas away and make JIT aware of them, via invokedynamic and MethodHandle optimizations.
Naturally they cannot rely on them being there, because Kotlin also needs to target ART, JS runtimes, WebAssembly and its own native version.
Even then, they benchmarked it, and inlining was still faster* than invokedynamic and friends, so they aren't changing it now JVM 1.8+ is a requirement.
* at the expense of expanded bytecode size
Naturally it is a requirement, JetBrains and Google only care about the JVM as means to launch their Kotlin platform, pity that they aren't into making a KVM to show Kotlin greatness.
If it feels salty, I would have appreciated if Android team was honest about Java vs Kotlin, but they weren't and still aren't.
If they were, both languages would be supported and compete on merit, instead of sniffling one to push their own horse.
Even on their Podcast they reveal complete lack of knowledge where Java stands.
PS Yes, I know, there is some weird way to disable it. Somehow that way changes every version and is about as non-intuitive as possible. And trying to actually support the encapsulation is by a wide margin more work than it is worth.
On the other hand, Android doesn't even support Java 8. It supports the long-dead Java 7 plus a subset of Java 8 features. Android essentially froze their core application runtime in amber over ten years ago and have just been adding layer upon layer of compiler-level sugar ever since. The effect is an increasing loss of the benefit of being on the Java platform, in terms of code sharing.
I never understood why they do not track the OpenJDK versions. I don't work on Android apps.. but it seems mildly insane to basically have a weird almost-Java where you aren't even sure if you can use a given Java lib.
Ex: I just took a look at a dependency I'm using
https://github.com/locationtech/spatial4j
Can it be used on Android..? I have no idea
From what I understand it's a weird stack now where nobody is actually writing Java for Android.
I'm still waiting for the day I can write a Clojure app for my phone..
(and not a Dart chat app.. but something actually performant that uses the hardware to the full extent)
NIH syndrome
> (and not a Dart chat app.. but something actually performant that uses the hardware to the full extent)
I used to work on Android, quit two years ago and have used Flutter since, it's a breath of fresh air. It does use the hardware to the full extent, imo it's significantly more performant: it does an end-around all the ossified Android nonsense.
Yeah, I'm currently developing a Flutter app and also using flutter_rust_bridge to separate the business logic and I can hardly believe how enjoyable it is.
Other than the initial project setup which is a me and Nix flakes problem it all comes together pretty smoothly.
Second, modules' encapsulation is not what caused the migration difficulties from 8 to 9+, evidenced by the fact that it wasn't even turned on until JDK 16: https://openjdk.org/jeps/396. From JDK 9 through 15, all access remained the same as it was in 8. The reason a lot of stuff broke was the JDK 9 was the largest release ever, and it began changing internals after some years of stagnation. Many JDK 8 libraries had used those internals and had become dependent on them not changing - though there was no promise of backward compatibility - because there was no encapsulation.
Finally, the market clearly wants things like projects Loom and Panama and Valhalla, things that wouldn't have been possible without encapsulation (at least not without breaking programs that depend on internals over and over). It's like people complaining about the noise and dust that installing cable ducts causes and say, "nobody asked for this, we just asked for fast internet!"
Maybe Google could finally support latest Java versions on Android, instead of begrudgingly update when Kotlin lags behind Maven Central most used versions.
Which by the way is a Java 17 subset, not Java 8, when supporting Android versions below Android 12 isn't required.
Also not all Kotlin inlines are lambdas or even include method calls
And not all macros, but just the ones which expand to massive expressions
Think template expressions in C++ or proc macros in Rust
This reminds me of a similar lesson C/C++ compilers had to learn with the "register" keyword. Early versions treated the keyword as a mandate. As compiler optimizers became more refined, "register" was first a recommendation and then ultimately ignored.
The C++ inline keyword is treated similarly as well, with different metrics used of course.
EDIT:
Corrected reference to early C/C++ keyword from "auto" to "register".
Yes I did, my bad.
I was visualizing Scala method definitions and associated the language's type inference with keyword use, thus bringing C++'s "auto" keyword to mind when the long-since deprecated "register" keyword was the correct subject.
It would appear LLM's are not the only entities which can "hallucinate" a response. :-D
You are thinking of C's inline/static inline.
C++'s "inline" semantics (which are implied for constexpr functions, in-class-defined methods, and static constexpr class attributes) allow for multiple "weak" copies of a function or variable to exist with external linkage. Rather than just an optimization hint it's much more of a "I don't want to put this in any specific TU" these days.
Would you mind to explain what you mean?
Linking some GitHub repo does not explain anything.
What is "abuse", what is "magic" in this context?
The problem was overly-frequent inlining generating enormous expressions, causing a lot JIT phase and slow execution.
Doing so is a feature of high-end VM runtimes like the state of the art JVMs or JS runtimes.
Look up the architecture of Catalyst + Tungsten
https://www.databricks.com/glossary/catalyst-optimizer
Scala 3's macros support staged compilation, so you can have macros which create code in later stages at runtime.
https://docs.scala-lang.org/scala3/reference/metaprogramming...
it's hard to buy it, considering that many of those "fatigued" moved on Kotlin, led by their managers' bs talking points.
Scala had/has a lot of promise. But how the language is marketed/managed/maintained really let a lot of people down and caused a lot of saltiness about it. And that is before we talk about the church of type-safety.
Scala is a more powerful language than Kotlin. But which do you want? A language with decent support that all your devs can use, or a language with more power but terrible support and only your very best devs can really take advantage of. And I say this as someone writing a compiler in Scala right now. Scala has its uses. But trying to get physicists used to Python to use it isn't one of them. Although that probably says more about the data science folks than Scala.
PS The GP is right, they should have focused on support and fixing the problems with the Scala compiler instead of changing the language. The original language spec is the best thing the Scala devs ever made.
The fundamental issue is that fixing Scala 2 warts warranted an entirely new compiler, TASTy, revamped macros... There was no way around most of the migration pains that we've witnessed. And at least the standard library got frozen for 6+ years.
However I agree that the syntax is a textbook case of trying to fix what ain't broke. Scala 3's syntax improvements should have stuck to the new given/using keywords, quiet if/then/else, and no more overloaded underscore abuse.
The great new syntax is the very reason I don't want to even touch Scala 2 any more.
The syntax change is the absolute highlight in Scala 3. It makes the language so much better!
The only real problem was that it happened so late; at least a decade too late.
On the contrary, there was nothing wrong with Scala's marketing. What's damaged it is a decade of FUD and outright lies from the people marketing Kotlin.
The overreaching majority thinks that Scala 3 is objectively much better than Scala 2 ever was. That's at least what you hear just everywhere, besides the occasional outlier by some Scala 2 die hards.
Just see how great this worked out for Java (or Perl… ;-))!
/s
Checking the bug mentioned, it was fixed in 2022.
So, I’m wondering how one would upgrade to scala 3, while keeping old version of libraries?
Keeping updated libraries is a good practice (even mandatory if you get audits like PCI-DSS).
That part puzzled me more than the rest.
> I did it as usual - updating dependencies
but later
> After upgrading the library, performance and CPU characteristics on Scala 3 became indistinguishable from Scala 2.13.
So... he didn't upgrade everything at first? Which IMO makes sense, generally you'd want to upgrade as little as possible with small steps. He just got unlucky.
Pinning specific versions of transitive deps is fairly common in large JVM projects due to either security reasons or ABI compatibility or bugs
First, the "good practice" argument is just an attempt to shut down the discussion. God wanted it so.
Second, I rather keep my dependencies outdated. New features, new bugs. Why update, unless there's a specific reason to do so? By upgrading, you're opening yourself up to:
- Accidental new bugs that didn't have the time to be spotted yet.
- Subtly different runtime characteristics (see the original post).
- Maintainer going rogue or the dependency getting hijacked and introducing security issues, unless you audit the full code whenever upgrading (which you don't).
You can instead document exceptions for why all those vulnerabilities doesn't apply to your app, but that's sometimes more trouble.
(For scala-specific libs, there is a bit more nuance, because lib versions contain scala version + lib version, e.g. foolib:2.12_1.0.2 where 2.12 = scala version)
I was considerably less impressed by the reporting when I finally found out the culprit.
Sure it was “Scala 3” … but not really.
It was an interaction of factors and I don’t think it would take away from the story to acknowledge that up front.
The normal way.
> Keeping updated libraries is a good practice
So is changing one thing at a time, especially when it's a major change like a language version upgrade.
If performance is a feature it needs to be written in the code. Otherwise it implicitly regresses when you reorder a symbol and you have no recourse to fix it, other than fiddling to see if it likes another pattern.
The JVM is extremely mature and performant, and JVM-based languages often run 5x (or more) than non-JVM high-level languages like Python or Ruby.
> Turns out there was indeed a subtle bug making chained evaluations inefficient in Scala 3
I’m comparing with Haskell, Scheme, or even SQl which all promise to compile efficient code from high level descriptions.
Lower-level languages don’t have this same problem to the same extent. They have other problems Scala doesn’t have.
Of course they have.
If the computer would directly execute what you write down in what you call "low level language" this would be slow as fuck.
Without highly optimizing compilers even stuff like C runs pretty slow.
If something about the optimizer or some other translation step of a compiler changes this has often significant influence on the performance of the resulting compilation artifacts.
1. The rules for when an optimization works and doesn't is complex and implicit (as seen in this post).
2. The magnitude of performance between getting an optimization and not is significant.
3. When it does fail, you have almost no recourse because you can't express your intent.
Now do C. You have problem 1, but not to the same extent. In rare cases you can see problem 2. And you never have problem 3.
It was about translation strategies and macro expansion.
But this makes no difference. You have all issues you just named exactly the same in so called "high level languages" as you have in C. C is in fact a high level language, and the code you write in C has not much in common with what the machine actually executes. The last time this wasn't like that is about 40 years ago.
1. Whether the C optimizer kicks in or not is pure dark magic. Nobody can tell from just looking at the code. The optimization techniques are way too complex to be understood ad hoc, not even experts can do that.
2. The difference between the optimizer doing its work, or instead the computer just verbatim executing whatever someone written down is hilariously large! Adding -O2 can make your code many orders of magnitude faster. Or it does almost nothing… (But like said, you can't know what will happen just from looking at the code, that's point again 1.)
3. You neither can express what the machine does in C. The machine does not execute anything like C. The last time it did is over 50 years ago… Since at least 30 years we have kind of JIT compiler in the CPUs which translate the result of compilation to ASM into the actual machine language. A modern CPU needs actually to emulate a computer that still works like a PDP-11 to match the C machine model even the real hardware does not look anything like a PDP-11 any more! You have only very indirect influence on the actual machine code when writing C. It's mostly about forcing the CPU internal JIT to do something, but you have no control over it, exactly as you don't have control over what for example the JVM JIT does. It's the exact same situation, just a level lower.
PS Perhaps they should make an actual unit test suite for their compiler. Instead they have a couple of dozen tests and have to guess if their compiler PR will break things.
It's really a shame because in many ways I do think it is a better language than anything else that is widely used in industry but it seems the world has moved on.
I used Scala for a bit around that period. My main recollection of it is getting Java compiler errors because Scala constructs were being implemented with deeply nested inner classes and the generated symbol names were too long.
Sounds like you've used some beta version over 15 years ago.
Nothing like described happens in current Scala and it's like that as long as I can think back. Never even heard of such bugs like stated.
Coming up with such possibly made up stuff over 15 years later sounds like typical FUD, to be honest.
No it didn't. Scala is powering some of the biggest companies on this planet.
https://business4s.org/scala-adoption-tracker/
It does apparently so well that nobody is even talking about it…
So it seems even better than all the languages people are "talking" (complaining) about.
I'm really hoping that https://flix.dev/ will learn from the mistakes of Scala. I t looks like a pretty nice spiritual successor to Scala.
We had a similar experience moving Ruby 2->3, which has a ton of performance improvements. It was in fact faster in many ways but we had issues with RAM spiking in production where it didn't in the past. It turned out simply upgrading a couple old dependencies (gems) to latest versions fixed most of the issues as people spotted similar issues as OP.
It's never good enough just to get it running with old code/dependencies, always lots of small things that can turn into bigger issues. You'll always be upgrading the system, not just the language.
Scala is a great language and I really prefer its typesafe and easy way to write powerful programs: https://www.lihaoyi.com/post/comlihaoyiScalaExecutablePseudo... Its a great Python replacement, especially if your project is not tied to ML libraries where Python is defacto, like JS on web.
I can thoroughly recommend it. Once of the best languages out there in terms of expressive power.
21 more comments available on Hacker News