[HN Gopher] Java is fast, code might not be
___________________________________________________________________
Java is fast, code might not be
Author : siegers
Score : 180 points
Date : 2026-03-20 13:43 UTC (9 hours ago)
(HTM) web link (jvogel.me)
(TXT) w3m dump (jvogel.me)
| koakuma-chan wrote:
| As much as I love Java, everybody should just be using Rust. That
| way you are actually in control, know what's going on, etc.
| Another reason specifically against Java is that the tooling,
| both Maven and Gradle, still stucks.
| krona wrote:
| > That way you are actually in control
|
| Programming in Rust is a constant negotiation with the
| compiler. That isn't necessarily good or bad but I have far
| more control in Zig, and flexibility in Java.
| koakuma-chan wrote:
| Yes, there is a learning curve to Rust, but once you get
| proficient, it no longer bothers you. I think this is more
| good than bad, because, for example, look at Bun, it is
| written in Zig, it has so many bugs. They had a bug in their
| filesystem API that freezed your process, and it stayed
| unfixed for at least half a year after I filed it. Zig is a
| nice C replacement, but it doesn't have the same correctness
| guardrails as Rust.
| krona wrote:
| Assuming we're talking about the same bug, The filesystem
| API freeze wasn't caused by Zig's lack of correctness
| guarantees, but a design flaw in Bun's implementation.
| dryarzeg wrote:
| Maybe I'm stupid, but I never actually understood people
| who blame programming languages for bugs in software.
| Because sure, it's good to have guardrails, but in my
| opinion, if you're writing a program and there's a bug,
| unless this bug lies somewhere in implementation of
| compiler/interpreter/etc, you can't blame the tooling,
| It's you who introduced this bug. It was your mistake.
|
| It's cool when your tooling warns you about potential
| bugs or mistakes in implementation, but it's still your
| responsibility to write the correct code. If you pick up
| a hammer and hit your finger instead of the nail, then in
| most cases (though not always) it's your own fault.
| chuckadams wrote:
| When millions of users constantly make the same mistake
| with the tool, there may be a problem with the tool,
| whether it's a defect in the tool or just that it's
| inappropriate for the job. Blaming the user might give
| one a righteous feeling, but decade after decade that
| approach has failed to actually fix any problems.
| dryarzeg wrote:
| That's why I say "in most cases" - so not always,
| actually. There might be problems with tools, I'm not
| trying to deny that. And by the way, what if some (or
| even most) of the users just don't have enough skill to
| use the tool properly? Again, there _could be a problem
| with tool, yes_ , but you can't always blame only tools
| for mistakes users make.
| koakuma-chan wrote:
| https://github.com/oven-sh/bun/issues/18192
|
| I am talking about this bug. It looks like it is still
| unfixed, in the sense, there is a PR fixing it, but it
| wasn't merged. LOL.
|
| Regardless of whether this specific bug would be caught
| by Rust compiler, Bun in general is notorious for
| crashing, just look at how many open issues there are,
| how many crashes.
|
| Not saying that you cannot make a correct program in Zig,
| but I prefer having checks that Rust compiler does, to
| not having them.
| j-vogel wrote:
| I'm a fan of Rust too. But there are millions of Java
| applications running in production right now, and some of them
| are running these anti-patterns today. Not everyone has the
| option to rewrite in a different language. For those teams,
| knowing what to look for in a profiler can make a real
| difference without changing a single dependency.
| koakuma-chan wrote:
| I think that right now it is easier than ever to rewrite your
| app in Rust, due to LLMs. Unfortunately there are still
| people out there who dismiss this idea, and continue having
| their back-end written in much inferior languages, like
| JavaScript or Python. If your back-end is written in Java,
| you aren't even in the worst spot.
| kykat wrote:
| "You think" is cheap, try doing it, rewrite an existing
| library in rust and see how it goes. Doing a rough
| prototype is easy, but the real work starts after that.
| piva00 wrote:
| Gradle does suck, it gives too much freedom on a tool that
| should be straightforward and actively design to avoid
| footguns, it does the opposite by providing a DSL that can
| create a lot of abstractions to manage dependencies. The only
| place I worked where the Gradle configuration looked somewhat
| sane had very strict design guidelines on what was acceptable
| to be in the Gradle config.
|
| Maven on the other hand, is just plain boring tech that works.
| There's plenty of documentation on how to use it properly for
| many different environments/scenarios, it's declarative while
| enabling plug-ins for bespoke customisations, it has cruft from
| its legacy but it's quite settled and it just works.
|
| Could Maven be more modern if it was invented now? Yeah, sure,
| many other package managers were developed since its inception
| with newer/more polished concepts but it's dependable, well
| documented, and it just plain works.
| koakuma-chan wrote:
| I would disagree that either "plain works" because to even
| package your app into a self-contained .jar, you need a
| plugin. I can't recall the specifics now, but years ago I
| spent many hours fighting both Maven and Gradle.
| looperhacks wrote:
| You "need a plugin" in the sense that every component of
| maven is a "plugin". The core plugins give you everything
| you need to build a self-contained jar - if you wanted to,
| you don't even have to configure the plugins, if you want
| to write a long cli command instead.
| piva00 wrote:
| Well, yes? It's a feature provided by a plugin, like any
| other feature in Maven, you declare the plugin for creating
| a fat-jar or single-jar and use that. It's just some lines
| of XML configuration so it plain works.
|
| Like I said, it's not hypermodern with batteries included,
| and streamlined for what became more common workflows after
| it was created but it doesn't need workarounds, it's not
| complicated to define a plugin to be called in one of the
| steps of the lifecycle, and it's provided as part of its
| plugin architecture.
|
| I can understand spending many hours fighting Gradle, even
| I with plenty of experience with Gradle (begrudgingly, I
| don't like it at all) still end up fighting its idiocies
| but Maven... It's like any other tool, you need to learn
| the basics but after that you will only fight it if you are
| verging away from the well-documented usage (which are
| plenty, it's been battle-tested for decades).
| computerdork wrote:
| Just wrote a comment how I've always liked Maven. It's
| perfect for small and medium sized projects, and for service-
| oriented architectures/microservices - it seems like it was
| designed for this! It's main goal is to help you figure out
| the libraries that you're using and build them in a standard
| way.
|
| It isn't great for really strange and odd builds, but in that
| case, you should probably be breaking your project down into
| smaller components (each with it's own maven file) anyways.
| jayd16 wrote:
| Not knowing what's going on in Java is a personal problem. The
| language and jvm have its own quirks but it's no less knowable
| than any other compiler optimized code. The debugging and
| introspection tooling in Java is also best in class so I would
| say it's one of the more understandable run times.
|
| Gradle does suck and maven is ok but a bit ugly.
| dionian wrote:
| LLMs take the whole argument away. Yes, maven/gradle/sbt suck
| to work with. But now you can just generate it.
| dionian wrote:
| I've been using maven for 20+ years, gradle for 10? ant for
| 5 before that. sbt for 15. I've written custom plugins for
| all of them. I know them quite well, unfortunately.
|
| I use LLMs to maintain them now. I keep the build files
| simple. It was an inconvenience before, but a trifle now.
| computerdork wrote:
| Actually, I like Maven. It's perfect for code that is
| broken into medium-sized projects, which makes it great for
| service-oriented architectures (would have said
| microservices here instead, but think we're learning that
| breaking our services too finely down is generally not a
| good idea).
|
| Yeah, it seems like Maven is designed to build just one
| project with relatively little build-code (although,
| figuring out versioning of the libs used in your build can
| get tricky, but guessing this is how it is in most
| languages). It's still one of my favorites build tools for
| many situations.
| ActorNightly wrote:
| Lets look at Java in modern day.
|
| * Most mature Java project has moved to Kotlin.
|
| * The standard build system uses gradle, which is either
| groovy or kotlin, which gets compiled to java which then
| compiles java.
|
| * Log4shell, amongst other vulnerabilities.
|
| * Super slow to adopt features like async execution
|
| * Standard repo usage is terrible.
|
| There is no point in using Java anymore. I don't agree that
| Rust is a replacement, but between Python, Node, and C/C++
| extensions to those, you can do everything you need.
| computerdork wrote:
| Actually, really like maven, it's focus on building in
| standard way is fantastic (but agreed, it look messy, with
| all its xml and necessary versioning).
| shermantanktop wrote:
| I'll never understand the impulse to tell the entire world what
| to do based on your own personal preferences and narrow
| experiences.
|
| It gets a reaction, though, so great for social media.
| pjmlp wrote:
| Rust has no place other than deployment scenarios where any
| kind of automatic resource management, be it tracing GC or
| reference counting, is not wanted for, either due to technical
| reasons, or being a waste of time trying to change people's
| mindset.
| bearjaws wrote:
| JavaScript can be fast too, it's just the ecosystem and decisions
| devs make that slow it down.
|
| Same for Java, I have yet to in my entire career see enterprise
| Java be performant and not memory intensive.
|
| At the end of the day, if you care about performance at the app
| layer, you will use a language better suited to that.
| j-vogel wrote:
| Fair point on ecosystem decisions, that's basically the thesis
| of the post. These patterns aren't Java being slow, they're
| developers (myself included) writing code that looks fine but
| works against the JVM. Enterprise Java gets a bad rap partly
| because these patterns compound silently across large codebases
| and nobody profiles until something breaks.
| maccard wrote:
| My experience with the defaults in JavaScript is that they're
| pretty slow. It's really, really easy to hit the limits of an
| express app and for those limits to be in your app code. I've
| worked on JVM backed apps and they're memory hungry (well, they
| require a reallocation for the JVM) and they're slow to boot
| but once they're going they are absolutely ripping fast and
| your far more likely to be bottlenecked by your DB long before
| you need to start doing any horizontal scaling.
| wiradikusuma wrote:
| Compile it to native (GraalVM) and you can get it fast while
| consuming less memory. But now your build is slow :)
| maccard wrote:
| The minute a project has maven in it the build is slow.
| Don't even get me started on Gradle...
| FatherOfCurses wrote:
| "Enterprise Java"
|
| Factories! Factories everywhere!
| wood_spirit wrote:
| Yes! Obligatory link to the seminal work on the subject:
|
| https://gwern.net/doc/cs/2005-09-30-smith-
| whyihateframeworks...
| whattheheckheck wrote:
| Why do you think this plays out over and over again? What's
| the causal mechanisms of this strange attractor
| pron wrote:
| Well, JS is fast and Go is faster, but Java is C++-fast.
| kyrra wrote:
| First request latency also can really suck in Java before
| hotpathed code gets through the C2 compiler. You can warm up
| hotpaths by running that code during startup, but it's really
| annoying having to do that. Using C++, Go, or Rust gets you
| around that problem without having to jump through the hoops of
| code path warmup.
|
| I wish Java had a proper compiler.
| bombcar wrote:
| Do none of the JVMs do that? GraalVM?
| user3939382 wrote:
| My architecture builds a command registry in Clojure/JVM
| which runs as a daemon, the registry is shared by a
| dynamically generated babashka (GraalVM) shell that only
| includes whitelisted commands for that user. So for the user,
| unauthorized commands don't even exist, and I get my JVM app
| with no startup overhead.
| rileymichael wrote:
| the best way is via CRaC (https://docs.azul.com/crac/) but
| only a few vendors support it and there's a bit of process to
| get it setup.
|
| in practice, for web applications exposing some sort of
| `WarmupTask` abstraction in your service chassis that devs
| can implement will get you quite far. just delay serving
| traffic on new deployments until all tasks complete. that way
| users will never hit a cold node
| pjmlp wrote:
| They do, to add to another comment of mine elsewhere, JIT
| caches go all the way back to products like JRockit, and IBM
| JVM has and it for years in Maestro, now available as OpenJ9.
|
| Too many folks have this mindset there is only one JVM, when
| that has never been the case since the 2000's, after Java for
| various reasons started poping everywhere.
| a-dub wrote:
| i'd be curious about a head to head comparison of how much the
| c2 actually buys over a static aot compilation with something
| serious like llvm.
|
| if it is valuable, i'd be surprised you can't freeze/resume the
| state and use it for instantaneous workload optimized startup.
| looperhacks wrote:
| You can create a native executable with GraalVM. Alternatively,
| if you want to keep the JVM: With the ongoing project Leyden,
| you can already "pre-train" some parts of the JVM warm-up, with
| full AoT code compilation coming some time in the future.
| senkora wrote:
| And going the other direction, if you want your C++ binaries
| to benefit from statistics about how to optimize the steady-
| state behavior of a long-running process, the analogous
| technique is profile-guided optimization (PGO).
| Thaxll wrote:
| GraalVM has a lot of limitations, some popular lib don't work
| with it. From what I remember anything using reflection is
| painful to use.
| vbezhenar wrote:
| GraalVM is terrible. Eats gigabytes of memory to compile
| super simple application. Spends minutes doing that. If you
| need compiled native app, just use Golang.
| brabel wrote:
| I used to be really excited about GraalVM but this,
| together with limitations in what Java code can run
| (reflection must be whitelisted - i.e. pain) made me run
| away from it. I do use Go, but my favourite substitute for
| Java is actually Dart. It can run as a script, compile to a
| binary or to a multiplatform "fast" format (a bit like a
| jar), and performance wise it's par on par with Java! It's
| faster on some things, a bit slower on other... but in
| general, compiling to exe makes it extremely fast to start,
| like Go. I think it even shares some Go binary creation
| tooling since both are made by Google and I remember when
| they were implementing the native compiler, they mentioned
| something about that.
| dionian wrote:
| This is why I use java for long running processes, if i care
| about a small binary that launches fast, i just use something
| slower at runtime but faster at startup like python.
| AlotOfReading wrote:
| And then you get applications choosing the worst of both
| worlds, like bazel/blaze.
| packetlost wrote:
| Python startup time can be pretty abysmal too if you have a
| lot of imports.
| ptx wrote:
| This is addressed by PEP 810 (explicit lazy imports) in
| Python 3.15 (currently in alpha):
| https://peps.python.org/pep-0810/
| packetlost wrote:
| Yeah, but it requires code changes to matter
| cogman10 wrote:
| So long as you aren't in a docker container, The openjdk can
| do fast startup pretty trivially.
|
| There are options to turn on which cause the JVM to save off
| and reload compiled classes. It pretty massively improves
| performance.
|
| You can get even faster if you do that plus doing a jlink
| jvm. But that's more of a pain. The AOT cache is a lot
| simpler to do.
|
| https://openjdk.org/jeps/514
| belfthrow wrote:
| I really hate how completely clueless people on hn are about
| java. This is not, and has not been an issue for many many
| years in Java and even the most junior of developers know how
| to avoid it. But oh no, go and rust is alwaayssss the solution
| sure.
| bombcar wrote:
| Ah, but let's port rust to the JVM!
| pythonaut_16 wrote:
| Can you provide any examples or evidence of Java apps that
| prove this?
|
| Because in my experience as of 2026, Java programs are
| consistently among the most painful or unpleasant to interact
| with.
| belfthrow wrote:
| Crac / aot cache / ready now all can address this. Not even
| considering native aot. Multiple low latency trading
| systems across market markers, hedge funds and ibs prove
| this. But people just want to compare it to building a cli
| tool in go or rust.
| pythonaut_16 wrote:
| But like can you provide an actual example of an
| application?
|
| > But people just want to compare it to building a cli
| tool in go or rust.
|
| This seems like the key. HN is definitely biased towards
| simpler, smaller tools. (And that's not a bad thing!).
| The most compelling JVM stories I hear are all from much
| larger scale enterprise settings.
|
| Kafka being a good example. It's very good at what it
| does, but painful to manage and usually not worth the
| pain for anyone who's not in a mega enterprise.
| belfthrow wrote:
| Because in real life, real world applications software is
| large, long running and needs to be bulletproof. Clis are
| not powering the world's infrastructure via piped bash
| scripts. It really baffles me what people actually do as
| software engineers on here with some of the nonsense that
| gets thrown around.
| dionian wrote:
| IntelliJ IDEA is reasonably fast, but of course its hard to
| make a big desktop app in java be fast.
| pjmlp wrote:
| Excelsior JET, now gone, but only because GraalVM and OpenJ9
| exist now.
|
| The folks on embedded get to play with PTC and Aicas.
|
| Android, even if not proper Java, has dex2oat.
| pron wrote:
| You mostly need a recent JDK. Leyden has already cut down
| warmup by _a lot_ and is expected to continue driving it down.
|
| https://foojay.io/today/how-is-leyden-improving-java-perform...
|
| https://quarkus.io/blog/leyden-1/
| taeric wrote:
| I challenge the idea that first request latency is bottle
| necked by language choice. I can see how that is plausible,
| mind. Is it a concern for the vast majority of developers?
| bob1029 wrote:
| AOT is nice for startup time, but there are tradeoffs in the
| other direction for long tail performance issues in production.
|
| There are JITs that use _dynamic_ profile guided optimization
| which can adjust the emitted binary at runtime to adapt to the
| real world workload. You do not need to have a profile ahead of
| time like with ordinary PGO. Java doesn 't have this yet
| (afaik), but .NET does and it's a huge deal for things like
| large scale web applications.
|
| https://devblogs.microsoft.com/dotnet/bing-on-dotnet-8-the-i...
| titzer wrote:
| I worked on JVMs long ago (almost twenty years now). At that
| time most Java usage was for long-running servers. The runtime
| team staunchly refused to implement AOT caching for as long as
| possible. This was a huge missed opportunity for Java, as
| client startup time has always, always, always sucked. Only in
| the past 3-5 years does it seem like things have started to
| shift, in part due to the push for Graal native image.
|
| I long ago concluded that Java was not a client or systems
| programming language because of the implementation priorities
| of the JVM maintainers. Note that I say _priorities_ --they are
| extremely bright and capable engineers that focus on different
| use cases, and there isn't much money to be made from a client
| ecosystem.
| hrmtst93837 wrote:
| Gaming the JIT just to get startup times in line is a decent
| sign that Java's "fast" comes with invisible asterisks all over
| prod graphs. At some point you're managing the runtime, not the
| app.
|
| AOT options like GraalVM Native Image can help cold starts a
| lot, but then half your favorite frameworks breaks and you
| trade one set of hoops for another. Pick which pain you want.
| tripple6 wrote:
| Do good, don't do bad. Okay.
| abound wrote:
| I don't think that's a charitable take of the article. To many
| programmers, it wouldn't be obvious that some of these footguns
| (autoboxing, string concatenation, etc) are "bad", or what the
| "good" alternatives are (primitives, StringBuilder, etc).
|
| That said, the article does have the "LLM stank" on it, which
| is always offputting, but the content itself seems solid.
| liampulles wrote:
| Understanding algorithmic complexity (in particular, avoiding
| rework in loops), is useful in any language, and is sage advice.
|
| In practice though, for most enterprise web services, a lot of
| real world performance comes down to how efficiently you are
| calling external services (including the database). Just
| converting a loop of queries into bulk ones can help loads (and
| then tweaking the query to make good use of indexes, doing
| upserts, removing unneeded data, etc.)
|
| I'm hopeful that improvements in LLMs mean we can ditch ORMs
| (under the guise that they are quicker to write queries and the
| inbetween mapping code with) and instead make good use of SQL to
| harness the powers that modern databases provide.
| j-vogel wrote:
| Author here. DB and external service calls are often the
| biggest wins, thanks for calling that out.
|
| In my demo app, the CPU hotspots were entirely in application
| code, not I/O wait. And across a fleet, even "smaller" gains in
| CPU and heap compound into real cost and throughput
| differences. They're different problems, but your point is
| valid. Goal here is to get more folks thinking about other
| aspects of performance especially when the software is running
| at scale.
| PathOfEclipse wrote:
| My experience profiling is that I/O wait is never the
| problem. However, the app may actually be spending most of
| it's CPU time interacting with database. In general, networks
| have gotten so fast relative to CPU that the CPU cost of
| marshalling or serializing data across a protocol ends up
| being the limiting factor. I got a major speedup once just by
| updating the JSON serialization library an app used.
| cogman10 wrote:
| Easy to get wrong as well.
|
| There's a balance with a DB. Doing 1 or 2 row queries 1000
| times is obviously inefficient, but making a 1M row query can
| have it's own set of problems all the same (even if you need
| that 1M).
|
| It'll depend on the hardware, but you really want to make sure
| that anything you do with a DB allows for other instances of
| your application a chance to also interact with the DB. Nothing
| worse than finding out the 2 row insert is being blocked by a
| million row read for 20 seconds.
|
| There's also a question of when you should and shouldn't join
| data. It's not always a black and white "just let the DB handle
| it". Sometimes the better route to go down is to make 2 queries
| rather than joining, particularly if it's something where the
| main table pulls in 1000 rows with only 10 unique rows pulled
| from the subtable. Of course, this all depends on how wide
| these things are as well.
|
| But 100% agree, ORMs are the worst way to handle all these
| things. They very rarely do the right thing out of the box and
| to make them fast you ultimately end up needing to comprehend
| the SQL they are emitting in the first place and potentially
| you end up writing custom SQL anyways.
| philipwhiuk wrote:
| ORMs are a caching layer for dev time.
|
| They store up conserved programming time and then spend it
| all at once when you hit the edge case.
|
| _If_ you never hit the case, it 's great. As soon as you do,
| it's all returned with interest :)
| xigoi wrote:
| The question is why we don't have database management
| systems that integrate tightly with the progmming language.
| Instead we have to communicate between two different
| paradigms using a textual language, which is itself
| inefficient.
| runroader wrote:
| We tried that in 90's RAD environments like Foxpro and
| others. If it fits the problem, they were great! If not,
| it's even worse than with an ORM. They rarely fit today
| since they were all (or mostly) local-first or even
| local-only. Scaling was either not possible or pretty
| difficult.
| ivan_gammel wrote:
| The answer is simple: model optimized for storage and
| model designed for processing are two different things.
| The languages used to describe and query them have to be
| different.
| ghurtado wrote:
| > The languages used to describe and query them have to
| be different.
|
| Absolutely not.
|
| _That which is asserted without evidence can be
| dismissed without evidence._
| kerblang wrote:
| > Absolutely not.
|
| Can also be dismissed without evidence
| Shorel wrote:
| Because every single database vendor will try to lock
| down their users to their DBMS.
|
| Oracle is a prime example of this. Stored procedures are
| the place to put all business logic according to Oracle
| documentation.
|
| This caused backslash from escaping developers who then
| declared business logic should never be inside the
| database. To avoid vendor lock-in.
|
| There's no ideal solution, just tradeoffs.
| cogman10 wrote:
| > Because every single database vendor will try to lock
| down their users to their DBMS.
|
| I mean, that already happens. It's quite rare to see
| someone migrate from one database to another. Even if
| they stuck to pure SQL for everything, it's still a
| pretty daunting process as Postgres SQL and MSSQL won't
| be the same thing.
| ghurtado wrote:
| > It's quite rare to see someone migrate from one
| database to another.
|
| I'm not discounting the level of effort involved, but I
| think the reason you don't see this often is because it
| is rare that simply changing DBMS systems is beneficial
| in and of itself.
|
| And even if it was frictionless (ie: if we had discovered
| ORM Samarkanda), the real choices are so limited that
| even if you did it regularly, you would soon run out of
| DBMSs to try.
| mike_hearn wrote:
| https://permazen.io/ exists and is a simpler yet still
| very powerful way to think about databases (for java but
| the concepts are general).
|
| _But_ it 's only really efficient if it can run code
| right next to the data via fast access - ideally the same
| machine. The moment you have a DB running on separate
| hardware or far away from the client, it's going to be
| slower.
|
| SQL is a very compact way to communicate what you want
| from a complex database in a way that can be statically
| analyzed and dynamically optimized. It's also
| sandboxable. Not so easily to replace.
| liampulles wrote:
| I agree with you fully yes. One has to watch out for
| overwhelmingly large or locking queries.
| philipwhiuk wrote:
| > external services (including the database)
|
| Or even the local filesystem :)
|
| CPU calls are cheap, memory is pretty cheap, disk is bad,
| spinning disk is very bad, network is 'good luck'.
|
| You can O(pretty bad) most of the time as long as you stay
| within the right category of those.
| sigbottle wrote:
| > Understanding algorithmic complexity (in particular, avoiding
| rework in loops), is useful in any language, and is sage
| advice.
|
| I recently fixed a treesitter perf issue (for myself) in neovim
| by just dfsing down the parse tree instead of what most
| textobject plugins do, which is:
|
| -> walk the entire tree for all subtrees that match this
| metadata
|
| -> now you have a list of matching subtrees, iterate through
| said subtree nodes, and see which ones are "close" to your
| cursor.
|
| But in neovim, when I type "daf", I usually just want to delete
| the function right under my cursor. So you can just implement
| the same algorithm by just... dfsing down the parse tree (which
| has line numbers embedded per nodes) and detecting the matches
| yourself.
|
| In school, when I did competitive programming and TCS, these
| gains often came from super clever invariants that you would
| just sit there for hours, days, weeks, just mulling it over.
| Then suddenly realize how to do it more cleverly and the entire
| problem falls away (and a bunch of smart people praise you for
| being smart :D). This was not one of them - it was just, "go
| bypass the API and do it faster, but possibly less
| maintainably".
|
| In industry, it's often trying to manage the tradeoff between
| readability, maintainability, etc. I'm _very_ much happy to
| just use some dumb n^2 pattern for n <= 10 in some loop that I
| don't really care much about, rather than start pulling out
| some clever state manipulation that could lead to pretty
| "menial" issues such as:
|
| - accidental mutable variables and duplicating / reusing them
| later in the code
|
| - when I look back in a week, "What the hell am I doing here?"
|
| - or just tricky logic in general
|
| I only noticed the treesitter textobject issue because I
| genuinely started working with 1MB autogen C files at work.
| So... yeah...
|
| I _could_ go and bug the maintainers to expose a "query over
| text range* API (they only have query, and node text range
| separately, I believe. At least of the minimal research I have
| done; I haven't kept up to date with it). But now that ties
| into considerations far beyond myself - does this expose state
| in a way that isn't intuitive? Are we adding _composable
| primitives_ or just ad hoc adding features into the library to
| make it faster because of the tighter coupling? etc. etc.
|
| I used to think of all of that as just kind of "bs accidentals"
| and "why shouldn't we just be able to write the best algorithms
| possible". As a maintainer of some systems now... nah, the
| architectural design is sometimes more fun!
|
| I may not have these super clever flashes of insight anymore
| but I feel like my horizons have broadened (though part of it
| is because GPT Pro started 1 shotting my favorite competitive
| programming problems circa late 2025 D: )
| liampulles wrote:
| You are not wrong. There are of course tradeoffs here. There
| are various things that can improve web service performance,
| but if we are talking about the performance of a web service
| in comparison to other more general concerns, like
| maintainability, then I agree trying to make small
| performance wins falls pretty low on the list.
|
| After all, even if one has some slow and beastly, unoptimized
| Spring Boot container that chews through RAM, its not that
| expenseive (in the grand scheme of things) to just replicate
| more instances of it.
| Seattle3503 wrote:
| > I'm hopeful that improvements in LLMs mean we can ditch ORMs
| (under the guise that they are quicker to write queries and the
| inbetween mapping code with) and instead make good use of SQL
| to harness the powers that modern databases provide.
|
| Maybe we can ditch active models like those we see in
| sqlalchemy, but the typed query builders that come with ORMs
| are going to become more important, not less. Leveraging the
| compiler to catch bad queries is a huge win.
| liampulles wrote:
| I use Ecto with Elixir in my day job, and it has a pretty
| good query building type solution. BUT: I still regularly
| come into issues where I have to use a fragment in order to
| do the specific SQL operation that I want, or I start my app
| and it turns out it has not caught the issue with my query
| (relating to my specific MySQL version or whatever). Which
| unfortunately defeats the purpose.
|
| My experience with something like the latest Claude Code
| models these days has been that they are pretty good at SQL.
| I think some combination of LLM review of SQL code with smoke
| tests would do the trick here.
| ackfoobar wrote:
| > ditch ORMs ... make good use of SQL
|
| I think Java (or other JVM languages) are then best positioned,
| because of jooq. Still the best SQL generation library I've
| used.
| vincnetas wrote:
| thumbs up for jooq
| matwood wrote:
| Anytime I use a language other than Java it's always jooq
| that I miss. It's that good.
| laughing_man wrote:
| I've always found ORMs to be performance killers. It always
| worked out better to write the SQL directly. The idea that you
| should have a one-to-one correspondence between your data
| objects and your database objects is disastrous unless your
| data storage is trivial.
| chopin wrote:
| I worked with ORM (EclipseLink) and used SQL just fine.
|
| When using JDBC I found myself quickly in implementing a poor
| mans ORM.
| roegerle wrote:
| EclipseLink never received enough love.
| nitwit005 wrote:
| The issue of creating a DB wrapper doesn't go away by using
| an ORM. One of the complaints about ORMs I have, in
| practice, is people often create another wrapper around it.
| matwood wrote:
| > Just converting a loop of queries into bulk ones can help
| loads
|
| This is usually the first thing I look for when someone is
| complaining about speed. Developers often miss it because they
| are developing against a database on their local machine which
| removes any of the network latency that exists in deployed
| environments.
| zadikian wrote:
| Well before LLMs, I already ditched ORMs. What sometimes holds
| back SQL is not having a convenient way to call it. Statically-
| typed languages require you to manually set result types unless
| you use a compile-time query builder, but that's a whole can of
| worms. Besides that, many client libs aren't so convenient out
| of the box, so you still need a few of your own helpers.
|
| Also, before jsonb existed, you'd often run into big blobs of
| properties you don't care to split up into tables. Now it takes
| some discipline to avoid shoving things into jsonb that
| shouldn't be.
| fzeindl wrote:
| > a lot of real world performance comes down to how efficiently
| you are calling external services (including the database)
|
| Apart from that my experience over the last 20 years was that a
| lot of performance is lost because of memory allocation (in
| GCed languages like Java or JavaScript). Removing allocation in
| hot loops really goes a long way and leads to 10 or 100 fold
| runtime improvements.
| kykat wrote:
| This has been the key for me as well, memory allocation in
| hot paths is usually the first optimization that I look for.
| It's quite surprising to see how far very inefficient
| algorithms (time complexity wise) can go as long as no
| allocations are made.
| gmueckl wrote:
| This applies to non-GC languages as well. Memory management
| is slow. Even with manual memory management I have been able
| to dramatically speed up code simply by modifying how memory
| is allocated.
|
| Parts of the GC language crowd in particular have come to
| hold some false optimistic beliefs about how well a GC can
| handle allocations. Also, Java and C# can sneak in silly heap
| allocations in the wrong places (e.g. autoboxing). So there
| is a tendency for programs to overload the GC with avoidable
| work.
| mrsmrtss wrote:
| Autoboxing is more a Java problem mainly because of type
| erasure with generics. C# has "proper" generics and no
| hidden boxing is occuring there.
| kykat wrote:
| I've been using sqlx + postgres very successfully with claude
| in the last couple of months. However, we've been raw dogging
| MySQL and node.js at work for over a year, and I also used raw
| SQLite from C++ before that (I am still traumatized by all the
| pointers, never again), so...
| jandrewrogers wrote:
| You can write many of the bad examples in the article in any
| language. It is just far more common to see them in Java code
| than some other languages.
|
| Java is only fast-ish even on its best day. The more typical
| performance is much worse because the culture around the language
| usually doesn't consider performance or efficiency to be a
| priority. Historically it was even a bit hostile to it.
| steve1977 wrote:
| Which, to be fair, in many cases is ok. If you just need to
| churn out LOB apps for worker drones as cheap as possible,
| performance is probably not the most important factor.
| this_user wrote:
| Performance is really not Java's issue. Even bad Java code is
| still substantially faster than the bulk of modern software
| that is based on technologies like Python or
| JavaScript/Node.js.
| comrade1234 wrote:
| Also finding the right garbage collector and settings that works
| best for your project can help a lot.
| zvqcMMV6Zcr wrote:
| > Exceptions for Control Flow
|
| This one is so prevalent that JVM has an optimization where it
| gives up on filling stack for exception, if it was thrown over
| and over in exact same place.
| j-vogel wrote:
| Author here. Great callout. That's the
| -XX:+OmitStackTraceInFastThrow optimization, been around since
| JDK 5. The C2 compiler detects exceptions thrown repeatedly
| from the same site and starts reusing a preallocated instance
| without filling the stack trace. Good for performance, but it
| makes debugging harder in production since you lose the trace.
| You can disable it with -XX:-OmitStackTraceInFastThrow if you
| need the traces back.
| dust-jacket wrote:
| ah, thank you. Haven't worked in java for a bit now, but that
| was the only one I read where I was like "I'm sure we didn't
| have to avoid this when I worked on java".
|
| The rest were all very familiar. Well, apart from the new
| stuff. I think most of my code was running in java 6...
| wood_spirit wrote:
| A subject close to my heart, I write a lot of heavily optimised
| code including a lot of hot data pipelines in Java.
|
| And aside from algorithms, it usually comes down to avoiding
| memory allocations.
|
| I have my go-to zero-alloc grpc and parquet and json and time
| libs etc and they make everything fast.
|
| It's mostly how idiomatic Java uses objects for everything that
| makes it slow overall.
|
| But eventually after making a JVM app that keeps data in
| something like data frames etc and feels a long way from J2EE
| beans you can finally bump up against the limits that only
| c/c++/rust/etc can get you past.
| polothesecond wrote:
| > And aside from algorithms, it usually comes down to avoiding
| memory allocations.
|
| I've heard about HFT people using Java for workloads where
| micro optimization is needed.
|
| To be frank, I just never understood it. From what I've seen
| heard/you have to write the code in such a way that makes it
| look clumsy and incompatible with pretty much any third party
| dependencies out there.
|
| And at that point, why are you even using Java? Surely you
| could use C, C++, or any variety of popular or unpopular
| languages that would be more fitting and ergonomic (sorry but
| as a language Java just feels inferior to C# even). The biggest
| swelling point of Java is the ecosystem, and you can't even
| really use that.
| yunnpp wrote:
| I am very interested about this and would like an
| authoritative answer on this. I even went as far as buying
| some books on code optimization in the context of HFT and I
| was not impressed. Not a single snippet of assembly; how are
| you optimizing anything if you don't look at what the
| compiler produces?
|
| But on Java specifically: every Java object still has a
| 24-byte overhead. How doesn't that thrash your cache?
|
| The advice on avoiding allocations in Java also results in
| terrible code. For example, in math libraries, you'll often
| see void Add(Vector3 a, Vector3 b, Vector3 our) as opposed to
| the more natural Vector3 Add(Vector3 a, Vector3 b). There you
| go, function composition goes out the window and the
| resulting code is garbage to read and write. Not even C is
| that bad; the compiler will optimize the temporaries away. So
| you end up with Java that is worse than a low-level
| imperative language.
|
| And, as far as I know, the best GC for Java still incurs no
| less than 1ms pauses? I think the stock ones are as bad as
| 10ms. How anyone does low-latency anything in Java then
| boggles my mind.
| yrxuthst wrote:
| Modern ZGC guarantees under 1ms pause times, and Azul's
| pauseless C4 has been around for a while too.
| dikaflowt wrote:
| Can you share the libs you 're using?
| taspeotis wrote:
| Knock Knock
|
| Who's there?
|
| _long pause_
|
| Java
| ackfoobar wrote:
| The premise of this joke is dead since 2020, when ZGC was
| production ready.
| victor106 wrote:
| this is great, so practical!!!
|
| any other resources like this?
| titzer wrote:
| For fillInStackTrace, another trick is to define your own
| Exception subclass and override the method to be empty. I learned
| this trick 15+ years ago.
|
| It doesn't excuse the "use exceptions for control flow" anti-
| pattern, but it is a quick patch.
| ivan_gammel wrote:
| God, please make me unsee it. That's a cool trick that turns
| into an anti-pattern itself if abused.
| hiyer wrote:
| I ran into 5 and 7 in a Flink app recently - was parsing a
| timestamp as a number first and then falling back to iso8601
| string, which is what it was. The flamegraph showed 10% for the
| exception handling bit. While fixing that, also found repeated
| creation of datetimeformatter. Both were not in loops, but both
| were being done for every event, for 10s of 1000s of events every
| second.
| wood_spirit wrote:
| (Perhaps a good library for timestamp code in data pipelines
| https://github.com/williame/TimeMillis)
| cmovq wrote:
| When you're using a programming language that naturally steers
| you to write slow code you can't only blame the programmer.
|
| I was listening to someone say they write fast code in Java by
| avoiding allocations with a PoolAllocator that would "cache"
| small objects with poolAllocator.alloc(),
| poolAllocator.release(). So just manual memory management with
| extra steps. At that point why not use a better language for the
| task?
| ablob wrote:
| You might have an application for which speed is not important
| most of the time. Only one or two processes might require
| allocation-free code. For such a case, why would you burden all
| of the other code with the additional complexity? Calling out
| to a different language then may come with baggage you'd rather
| avoid.
|
| A project might also grow into these requirements. I can easily
| imagine that something wasn't problematic for a long time but
| suddenly emerged as an issue over time. At that point you
| wouldn't want to migrate the whole codebase to a better
| language anymore.
| cogman10 wrote:
| Bad idea. I've made a pool allocator before, but that was for
| expensive network objects and expensive objects dealing with
| JNI.
|
| Doing it to avoid memory pressure generally means you simply
| have a bad algorithm that needs to be tweaked. It's very rarely
| the right solution.
| gf000 wrote:
| Not sure why you are down voted. Depending on how its used it
| could actually be detrimental to performance.
|
| The JVM may optimize many short lived objects better than a
| pool of objects with less reasonably lifetimes.
| cogman10 wrote:
| This is the second time this week on HN that I've seen
| people suggesting object pools to solve memory pressure
| problems.
|
| I generally think it's because people aren't experienced
| with diagnosing and fixing memory pressure. It's one of the
| things I do pretty frequently for my day job. I'm fortunate
| enough to be the "performance" guy at work :).
|
| It'll always depend on what the real issue is, but
| generally speaking the problem to solve isn't reinventing
| garbage collection, but rather to eliminate the reason for
| the allocation.
|
| For example, a pretty common issue I've seen is copying a
| collection to do transformations. Switching to streams,
| combining transformation operations, or in an extreme case,
| I've found passing around a consumer object was the way to
| avoid a string of collection allocations.
|
| Even the case where small allocations end up killing
| performance, for example like the autoboxing example of the
| OP, often the solution is to either make something mutable
| that isn't, or to switch to primitives (Valhalla can't come
| soon enough).
|
| Heck, sometimes even an object cache is the right solution.
| I've had good success reducing the size of objects on the
| heap by creating things like `Map<String, String>` and then
| doing a `map.computeIfAbsent(str, Function.identity());`
| (Yes, I know about string interning, no I don't want these
| added to the global intern cache).
|
| Regardless, the first step is profiling (JFRs and heap
| dumps) to see where memory is spent and what is dominating
| the allocation rate. That's a first step that people often
| skip and jump straight to fixing what they think is broken.
| ivan_gammel wrote:
| TBH, I do not see how Java as a language steers anyone to use
| one those shotguns. E.g. the knowledge about algorithmic
| complexity is foundational, the StringBuilder is junior-level
| basic knowledge.
| nightpool wrote:
| How would you handle validating numeric input in a hot path
| then? All of the solutions proposed in #5 are incomplete or
| broken, and it stems from the fact that Java's language
| design over-uses exceptions for error handling in places
| where an optional value would be much safer and faster.
| ivan_gammel wrote:
| Normally in 100% cases, with parseInt/parseDouble etc.
| Getting NumberFormatException so frequently on a hot path
| that it impacts performance means, that you aren't solving
| the parsing number problem, you are solving a guessing type
| problem, which is out of scope for standard library and
| requires custom parser.
| nightpool wrote:
| Okay, but this contradicts your original statement that
| "Java doesn't steer anyone to use these [footguns]".
| Every language has a way to parse integers, and most
| developers do not need a custom parser. Only in Java does
| that suddenly become a performance footgun.
| ivan_gammel wrote:
| It does not. If you need to parse a number, you use
| standard library and you will be fine. The described case
| with huge impact on hot path is the demonstration why
| using brains is important. The developer that will get
| into this mess is the one who will find the way to
| suffocate his code with performance bottlenecks in
| thousand other ways. It's not a language or library
| problem.
| dionian wrote:
| Yes, parseInt et al work very fast for good inputs. What
| percentage of your inputs are invalid numbers and why ?
| ivan_gammel wrote:
| > What percentage of your inputs are invalid numbers and
| why ?
|
| This is a wrong question to ask in this context. The
| right question to ask is when actually exceptional flow
| becomes a performance bottleneck. Because, obviously, in
| a desktop or even in a server app validating single user
| input even 99% of wrong inputs won't cause any trouble.
| It may become a problem with bulk processing, but then,
| and I have to repeat myself here, it is no longer a
| number parsing problem, it's a problem of not
| understanding what your input is.
| kerblang wrote:
| > Java's language design over-uses exceptions for error
| handling
|
| No, _library authors '_ design over-uses exceptions. Also
| refer to people using exceptions to generate 404 http
| responses in web systems - hey, there's an easy DDOS...
| This _can_ include some of Java 's standard libraries,
| although nothing springs to mind.
|
| Exceptions are not meant for mainstream happy-path
| execution; they mean that something is broken. Countless
| times I have had to deal garbage-infested logs where one
| programmer is using exceptions for rudimentary validation
| and another is dumping the resulting stack traces left and
| right as if the world is coming to end.
|
| It is a problem, but it's an abuse problem, not a standard
| usage problem.
| ekkeke wrote:
| This point gets raised every single time managed languages and
| low latency development come up together. The trade off is
| running "fast" all of the time, even when you don't have to, vs
| running slow most of the time and tinkering when you need to go
| fast.
|
| I've spent a fair few years developing lowish (10-20us wire to
| wire) latency trading systems and the majority of the code does
| not need to go fast. It's just wasted effort, a debugging
| headache, and technical debt. So the natural trade off is a bit
| of pain to make the hot path fast through spans, unsafe code,
| pre-allocated object pools, etc and in return you get to use a
| safe and easy programming language everywhere else.
|
| In C# low latency dev is not even that painful, as there are a
| lot of tools available specifically for this purpose by the
| runtime.
| d_burfoot wrote:
| > So just manual memory management with extra steps
|
| This is actually the perfect situation: you are allowed to do
| it carefully and manually for 1% of code on the hot path, but
| you don't have to worry about it for the 99% of the code that's
| not.
| laughing_man wrote:
| Java doesn't steer you into object pools. I wrote Java code for
| 20 years and never used a cache to avoid allocating objects,
| and never saw a colleague use one. The person you were talking
| to doesn't know what he's doing.
| cxr wrote:
| The problem with comments like these is that guessing what
| "better language" a commentator has in mind is always an
| exercise left up to the reader. And that tends to be by design
| --it's great for potshots and punditry, because it means not
| having make a concrete commitment to anything that might
| similarly be confronted and torn apart in the replies--like if
| the "better language" alluded to is C (and it generally
| is)--the language where the standard library "steers" you
| towards quadratic string operations because the default/natural
| way to refer to a string's length is O(n).
| andai wrote:
| I decompiled Project Zomboid (written in Java) a while back,
| because I was curious about the performance issues I was having
| with the game. (Very laggy on my 10 year old laptop, while
| looking like The Sims 1.) I figured, best case scenario I find
| some easy bottlenecks and I can patch in a fix.
|
| Well, the whole thing was standard Java OOP, except they also
| had a bunch of functional programming stuff on top of that. I
| can relate to that -- I think they were university students
| when they started, and I definitely had an OOP and FP phase.
| But then they just... kept it, 10+ years later.
|
| So while it's true that you can write C in any language...
| those kind of folks don't tend to use Java in the first place
| ;)
|
| --
|
| (Except Notch? Well, his code _looks_ like C, not sure if it 's
| actually fast! I really enjoyed his 4 kilobyte java games back
| in the day, I think he published the source for each one too.)
|
| EDIT: Found it!
|
| https://web.archive.org/web/20120317121029/http://www.mojang...
|
| Edit 2: This one has a download, still works!
|
| https://web.archive.org/web/20120301015921/http://www.mojang...
| bigwheels wrote:
| What is the ending of your story!? Did you find and fix some
| bottlenecks?
| kykat wrote:
| I saw something like that being suggested when working with GIS
| data with many points as classes in Java, the object overhead
| for storing XYZ doubles is quite crazy. The optimization was to
| build a global double array and use "pointers" to get and set
| the number in the array.
|
| Even JavaScript is much better for this, much, much better.
| lern_too_spel wrote:
| You're describing array of structs vs. struct of arrays. Even
| in JavaScript, you would have to manually do the latter.
| kykat wrote:
| V8 automatically optimizes objects with the same shape into
| efficient structs, making array of objects much more
| efficient than in Java.
|
| And the manually manager int array acts more like system
| memory, it's not continuous, so you could have point i 0
| and 2 and the data would be: [1, 2, 3, x, x,x, 3, 2, 1] (3D
| points).
|
| So I am not describing a struct of arrays.
| spankalee wrote:
| Hidden class optimizations just make JavaScript objects
| behave a little more like Java class instances, where the
| VM knows where to find each field, rather than having to
| look it up like a map.
|
| It doesn't make JS faster than Java, it makes it almost
| as fast in some cases.
| kykat wrote:
| I can only say what I observed in testing, and that's
| that having millions of instances of a class like
| Point3D{x, y, z} in JS uses significantly less memory
| than in Java (this was tested on Android, not sure if
| relevant). It was quite some time ago so I don't remember
| the details.
| spankalee wrote:
| JavaScript has the exact same issue - objects are on the heap
| and require allocation and pointer dereferencing. For huge
| collections of numbers, arrays _might_ be better.
|
| But JS has another problem: there's no way to force a number
| to be unboxed (no primitive vs boxed types), so the array of
| doubles might very well be an array of pointers to
| numbers[1].
|
| But with hidden class optimizations an object might be able
| to store a float directly in a field, so the array of objects
| will have one box per (x,y,z), while an array of "numbers"
| might have one box per number, so 3x as many. My guess is,
| without benchmarking, is that JS is much worse than Java
| then, because the "optimization" will end up being worse.
|
| [1]: Most JS engines have an optimization for small ints,
| called SMIs, that use pointer tagging to support either an
| int or a references, but I don't think they typically do this
| optimization for floats.
| wiseowise wrote:
| > At that point why not use a better language for the task?
|
| Such as?
| ww520 wrote:
| The autoboxing in a loop case can be handled by the compiler.
| kpw94 wrote:
| The Autoboxing example imo is a case of "Java isn't so fast". Why
| can't this be optimized behind the scenes by the compiler ?
|
| Rest of advice is great: things compilers can't really catch but
| a good code reviewer should point out.
| vbezhenar wrote:
| Why should compiler optimize obviously dumb code? If developer
| wants to create billions of heap objects, compiler should
| respect him. Optimizing dumb code is what made C++ unbearable.
| When you write one code and compilers generates completely
| different code.
| carlmr wrote:
| The problem is rather that Java doesn't have generics and
| structs, so you're kind of forced to box things or can't use
| collections.
| kllrnohj wrote:
| javac for better or worse is aggressively against doing
| optimizations to the point of producing the most ridiculously
| bad code. The belief tends to be that the JIT will do a better
| job fixing it if it has byte code that's as close as possible
| to the original code. But this only helps if a) the code ever
| gets JIT'd at all (rarely true for eg class initializers), and
| b) the JIT has the budget to do that optimization. Although
| JITs have the advantage of runtime information, they are also
| under immense pressure to produce any optimizations as fast as
| possible. So they rarely do the level of deep optimizations of
| an offline compiler.
| cogman10 wrote:
| Nitpick just because.
|
| Orders by hour could be made faster. The issue with it is it's
| using a map when an array works both faster and just fine.
|
| On top of that, the map boxes the "hour" which is undesirable.
|
| This is how I'd write it long[] ordersByHour =
| new long[24]; var deafultTimezone =
| ZoneId.systemDefault(); for (Order order : orders) {
| int hour = order.timestamp().atZone(deafultTimezone).getHour();
| ordersByHour[hour]++; }
|
| If you know the bound of an array, it's not large, and you are
| directly indexing in it, you really can't do any better
| performance wise.
|
| It's also not less readable, just less familiar as Java devs
| don't tend to use arrays that much.
| Okx wrote:
| maybe it would be a little better to use ints rather than
| longs, as Java lists can't be bigger than the int max value
| anyways. Saves you a cache line or two.
| cogman10 wrote:
| Fair point, but it is possible this isn't a list but rather
| some sort of iterable. Those can be boundless.
|
| Practically speaking, that would be pretty unusual. I don't
| think I've ever seen that sort of construct in my day to day
| coding (which could realistically have more than 1B
| elements).
| wood_spirit wrote:
| Also zap the timestamp instant objects if you really need
| speed; see https://github.com/williame/TimeMillis
| jerf wrote:
| Any non-trivial program that has never had an optimizer run on it
| has a minimal-effort 50+% speedup in it.
| Okx wrote:
| The code: public int parseOrDefault(String value,
| int defaultValue) { if (value == null ||
| value.isBlank()) return defaultValue; for (int i = 0; i
| < value.length(); i++) { char c = value.charAt(i);
| if (i == 0 && c == '-') continue; if
| (!Character.isDigit(c)) return defaultValue; }
| return Integer.parseInt(value); }
|
| Is probably worse than Integer.parseInt alone, since it can still
| throw NumberFormatExceptions for values that overflow (which is
| no longer handled!). Would maybe fix that. Unfortunately this is
| a major flaw in the Java standard library; parsing numbers
| shouldn't throw expensive exceptions.
| deepsun wrote:
| And it will fail with "-"
| j-vogel wrote:
| Good catches from several of you. The original fix dropped the
| try-catch entirely which was a regression for overflow and edge
| cases like a bare "-". Updated the post to keep a try-catch
| around the final parseInt as a safety net. The pre-validation
| still avoids the expensive path for the common cases, which is
| the core point. Appreciate the feedback.
| zahlman wrote:
| Not to mention it's extra work in the case where the input
| _usually is_ valid.
| spankalee wrote:
| Avoiding Java's string footguns is an interesting problem in
| programming languages design.
|
| The String.format() problem is most immediately a bad compiler
| and bad implementation, IMO. It's not difficult to special-case
| literal strings as the first argument, do parsing at compile
| time, and pass in a structured representation. The method could
| also do runtime caching. Even a very small LRU cache would fix a
| lot of common cases. At the very least they should let you make a
| formatter from a specific format string and reuse it, like you
| can with regexes, to explicitly opt into better performance.
|
| But ultimately the string templates proposal should come back and
| fix this at the language level. Better syntax and guaranteed
| compile-time construction of the template. The language should
| help the developer do the fast thing.
|
| String concatenation is a little trickier. In a JIT'ed language
| you have a lot of options for making a hierarchy of string
| implementations that optimize different usage patterns, and still
| be fast - and what you really want for concatenation is a
| RopeString, like JS VMs have, that simply references the other
| strings. The issue is that you don't want virtual calls for hot-
| path string method calls.
|
| Java chose a single final class so all calls are direct. But they
| should have been able to have a very small sealed class hierarchy
| where most methods are final and directly callable, and the
| virtual methods for accessing storage are devirtualized in
| optimized methods that only ever see one or two classes through a
| call site.
|
| To me, that's a small complexity cost to make common string
| patterns fast, instead of requiring StringBuilder.
| LtWorf wrote:
| Sometimes running strace on jvm software you will see some
| sycall patterns that are incredibly inefficient.
| brabel wrote:
| Yeah, Java is pretty fast despite the fact that it still has
| these kinds of obviously suboptimal things going on.
|
| I love how Zig, D and Rust do exactly what you say: parse the
| format string at compile time, making it super efficient at
| runtime (no parsing, no regex, just the optimal code to get the
| string you need).
|
| I say this but I write most of my code in Java/Kotlin :D . I
| just wish I could write more low-level languages for super
| efficient code, but for what I do, Java is more than enough.
| jandrewrogers wrote:
| > Zig, D and Rust
|
| Also C++, which works the same way.
| arikrahman wrote:
| I admire Rich Hickey's approach of building on top of the
| Java ecosystem for this reason, adding a functional first
| approach with emphasis on data structures, where using the
| right algorithms comes naturally.
| mike_hearn wrote:
| Kotlin string interpolation turns into the fancy
| invokedynamic based string concatenation behind the scenes so
| it's very optimized: https://openjdk.org/jeps/280
| rf15 wrote:
| > But ultimately the string templates proposal should come back
| and fix this at the language level.
|
| They tried, its opponents dilluted it to the point of
| uselessness and now will forever use this failed attempt as a
| wedge.
|
| I'm sorry, I don't believe Java will get sensible String
| templates in our life time.
| uraura wrote:
| I thought those were common sense until I worked on a program
| written by my colleague recently.
| Izkata wrote:
| "Java is slow" is a reputation it earned in the 90s/2000s because
| the JVM startup (at least on Windows) was extremely slow, like
| several seconds, with a Java-branded splash screen during that
| time. Even non-technical people made the association.
| marginalia_nu wrote:
| Java's start up speeds were greatly improved by the engineers
| at Oracle, but thankfully we invented springboot and guice to
| get around that problem.
| spwa4 wrote:
| Java IS fast. The time between deciding to use Java and Oracle's
| lawyers breaking down your door is measured in just weeks these
| days.
| latchkey wrote:
| When they say that AI will replace programmers, I think of this
| article and come to terms with my own job security.
|
| Most of this stuff is just central knowledge of the language that
| you pick up over time. Certainly, AI can also pick this stuff up
| instantly, but will it always pick the most efficient path when
| generating code for you?
|
| Probably not, until we get benchmarks into the hot path of our
| test suite. That is something someone should work on.
| sgbeal wrote:
| Slight correction:
|
| > StringBuilder works off a single mutable character buffer. One
| allocation.
|
| It's one allocation to instantiate the builder and _any_ number
| of allocations after that (noting that it's optimized to reduce
| allocations, so it's not allocating on every append() unless
| they're huge).
| seu wrote:
| I'm a bit surprised to see those examples, because there's
| nothing really new here. These are typical beginner pitfalls and
| have been there for at least a decade or more. Or maybe it's
| because I learned java in the late 90s and later used it for
| J2ME, and then using things like StringBuilder (StringBuffer in
| the old days) were almost mandatory, and you would be very
| careful trying to avoid unnecessary object allocations.
| larsnystrom wrote:
| I remember writing Java for our introductory programming course
| at university around 2010. I was already familiar with object
| oriented programming in PHP at the time, so I just wrote the
| Java code like I would write PHP. I was absolutely astounded at
| the poor performance of the Java app. I asked one of our tutors
| and I can still remember him looking at the code and saying
| something along the lines of "oh, you're instantiating objects
| in a loop, that's obviously going to be slow". Like, what? If I
| can do this performantly in freakin PHP, how can Java, the
| flagship of OOP, not have fast instantiation of objects? I'm
| still shaking my head thinking about it.
| zahlman wrote:
| Did you actually benchmark _that task_ against similarly-
| architected PHP code?
| lern_too_spel wrote:
| Your tutor misdiagnosed the issue. These allocations in a
| tight loop would have used bump allocation on Java 6 in 2010,
| and the young generation would have used a copy collector,
| which would have freed those objects more cheaply than any
| unspecialized malloc/free. It would have beaten the pants off
| of PHP's reference counting GC.
| layer8 wrote:
| For #5, the "fix" [0] is incomplete, because you will still get a
| NumberFormatException when the value is out of range. For _int_ ,
| you could check if there are more or less than 10 digits, and use
| parseLong() when there are exactly 10 digits. For _long_ , you
| can use BigInteger when there are exactly 18 digits. After
| skipping any leading zeros, of course. Or you could just
| replicate the JDK's parsing implementation and change the part
| where it throws NumberFormatException (at the possible cost of
| foregoing JIT intrinsics).
|
| A second bug is that Character.isDigit() returns true for non-
| ASCII Unicode digits as well, while Integer.parseInt() only
| supports ASCII digits.
|
| Another bug is that the code will fail on the input string "-".
|
| Lastly, using value.isBlank() is a pessimization over
| value.isEmpty() (or just checking value.length(), which is read
| anyway in the next line), given that the loop would break on the
| first blank character. It makes the function not be constant-
| time, along with the first point above that the length of the
| digit sequence isn't being limited.
|
| [0] public int parseOrDefault(String value,
| int defaultValue) { if (value == null ||
| value.isBlank()) return defaultValue; for (int i = 0;
| i < value.length(); i++) { char c =
| value.charAt(i); if (i == 0 && c == '-')
| continue; if (!Character.isDigit(c)) return
| defaultValue; } return
| Integer.parseInt(value); }
| zahlman wrote:
| > Accidental O(n2) with Streams Inside Loops
|
| Man that code looks awful. Really reminds me of why I drifted
| away from Java over time. Not just the algorithm, of course; the
| repetitiveness, the hoops that you have to jump through in order
| to do pretty "stream processing"... and then it's not even an FP
| algorithm in the end, either way!
|
| Honestly the only time I can imagine the "process the whole
| [collection] per iteration" thing coming up is where either you
| really do need to compare (or at least really are intentionally
| comparing) each element to each other element, or else this exact
| problem of building a histogram. And for the latter I honestly
| haven't seen people fully fall into this trap very often. More
| commonly people will try to iterate over the possible buckets
| (here, hour values), sometimes with a first pass to figure out
| what those might be. That's still extra work, but at least it's
| O(kn) instead of O(n^2).
|
| You _can_ do this sort of thing in an elegant, "functional"
| looking way if you sort the data first and then group it by the
| same key. That first pass is O(n lg n) if you use a classical
| sort; making a histogram like this in the first place is
| basically equivalent to radix sort, but it's nice to not have to
| write that yourself. I just want to show off what it _can_ look
| like e.g. in Python: def local_hour(order):
| return datetime.datetime.fromtimestamp(order.timestamp).hour
| groups = itertools.groupby(sorted(orders, key=local_hour),
| key=local_hour) orders_by_hour = {hour: len(list(orders))
| for (hour, orders) in groups}
|
| Anyway, overall I feel like these kinds of things are mostly done
| by people who don't need to have the problem explained, who have
| simply been lazy or careless and simply need to be made to look
| in the right place to see the problem. Cf. Dan Luu's anecdotes
| https://danluu.com/algorithms-interviews/ , and I can't seem to
| find it right now but the story about saving a company millions
| of dollars finding Java code that was IIRC resizing an array one
| element at a time.
|
| (Another edit: originally I missed that the code was only trying
| to count the number of orders in each hour, rather than
| collecting them. I fixed the code above, but the discussion makes
| less sense for the simplified problem. In Python we can do this
| with `collections.Counter`, but it wouldn't be unreasonable to
| tally things up in a pre-allocated `counts_by_hour = [0] * 24`
| either.)
|
| ----
|
| Edit:
|
| > String.format() came in last in every category. It has to...
| StringBuilder was consistently the fastest. The fix: [code not
| using StringBuilder]... Use String.format() for the numeric
| formatting where you need it, and let the compiler optimize the
| rest. Or just use a StringBuilder if you need full control.
|
| Yeah, this is confused in a way that I find fairly typical of LLM
| output. The attitude towards `String.format` is just plain
| inconsistent. And there's no acknowledgment of how multiple `+`s
| in a line get optimized behind the scenes. And the "fix" still
| uses `String.format` to format the floating-point value, and
| there's no investigation of what that does to performance or
| whether it can be avoided.
| EricRiese wrote:
| This is a Spring specific gripe and I know this blog post doesn't
| assume Spring, but I hate seeing `new ObjectMapper()`. Spring
| Boot auto configures an ObjectMapper for you and you probably
| want the customization it gives you, including `java.time`
| handling and classpath scanning. I've wrestled with so many bugs
| caused by not using the `ObjectMapper` bean.
| dionian wrote:
| well yeah Jackson is slow.
| computerdork wrote:
| Have always really liked Java, but yeah, Spring overall has
| been terrible for the language. Autowiring is against the
| principles of a typesafe programming language - Don't make me
| guess what what object is going to be attached to a reference.
| And if you do, at least figure out what linked object is at
| compile time, not at run time.
|
| Spring autowiring makes Java seem as a whole unnecessarily
| complex. Think it should be highly discouraged in the language
| (unless it is revamped and made apart of the compiler).
|
| ... not sure how this applies to the ObjectMapper, as I haven't
| programmed in Java in awhile. ... and my gripe doesn't apply to
| SpringBoot though:)
| pregnenolone wrote:
| Java really isn't fast unless you're basically writing C style
| Java. Abstracting is expensive in Java and if one can't abstract,
| what's the point of writing Java in 2026? After all these years
| value types aka Valhalla are still in a soon(tm) state. If it
| weren't for Project Loom, which is genuinely awesome, I wouldn't
| see any justification for using Java in this day and age.
| fnord77 wrote:
| The world has shown that it doesn't need all those intricate
| design patterns that java fosters.
|
| It does seem like java missed their chance, it's a shame.
| jimbokun wrote:
| Compared to what?
| dionian wrote:
| my hotspot JITd code is quite fast
| zmmmmm wrote:
| String concatenation in a loop is a 1990's era Java footgun. It's
| interesting the things that have persisted vs been cast aside.
| Very significant design decisions have been enforced on far less
| grounds than the stupidity of how the default String
| concatenation operator works.
| inglor_cz wrote:
| This. I learnt Java in 2004-5, when 1.4 was dominant and
| O'Reilly books were a much better training resource than
| whatever you found on the Internet.
|
| Concatenating of thousands of individual Strings was already
| considered a well-known performance killer back then.
|
| Interesting to see this in the wild in 2026.
| IsTom wrote:
| Does Java not have some kind of helper function to join
| strings? Why'd anyone write loops like that?
| tombert wrote:
| I think that the `sychronized` keyword in Java was a mistake.
|
| I've seen classes that are meant to be used by multiple threads
| where literally every method has `synchronized` because "that was
| the only way they could get it to work". Of course, if literally
| every method is synchronized it literally _can 't_ actually be
| used by multiple threads, it just looks like it is.
|
| Generally speaking I work pretty hard to avoid any kind of locks.
| Locks can be an anti-pattern in my mind: for a lot of problems,
| if I am reaching for a lock, it's because I haven't actually
| thought through the problems well enough. They're a bandaid and
| they create potential choke-points in the app. I also think that
| they're a crappy fix to try and shoehorn non-concurrent patterns
| into a concurrent landscape.
|
| I personally think that making something thread-safe and
| concurrent while also being maintainable and fast is a hard
| problem, and I think lazily trying to add threads into concurrent
| applications is a good way to write terrible code that is
| impossible to debug.
|
| Obviously no accounting for taste, but when I write programs now,
| I kind of always make them concurrent-first (generally using
| and/or reinventing the actor model). I try and build my initial
| algorithm to accept that concurrency is inevitable and start that
| from the get go. I can't remember the last time I reached for
| `synchronized`, though every now and then I do have to reach for
| ReentrantLock, and I always feel dirty doing so.
___________________________________________________________________
(page generated 2026-03-20 23:01 UTC)