I'm not sure what "no compile time safety in this stack" even means in the context of a strongly-typed compiled language.
If you are referring to the dependency injection container making use of reflection, then Spring Native graduated from experimental add-on to part of the core framework some years ago. You can now opt for Quarkus/Micronaut-style static build-time dependency injection, and even AOT compilation to Go-style native executables, if you're willing to trade off the flexibility that comes with avoiding reflection. For example, not being able to use any of the "@ConditionalOnXXX" annotations to make your DI more dynamic.
(Personally, I don't believe that those trade-offs are worth it in most cases. And I believe that all the Spring magic in the universe doesn't amount to 10% of what Python brings to the table in a minimal Django/Flask/FastAPI microservice. But the option is there if your use case truly calls for it.)
Honestly, I've never run into anyone who considers Spring to be "the bane of their existence", where the real issue wasn't simply that the bulk of their experience was in something else. Where they weren't thrown into someone else's project, and resent working with decisions made by other people, but don't want to either dig in and learn the tech or else search for a new job where they get to make the choices on a greenfield project.
My experience, from memory; a few of the details might be wrong (e.g. it probably wasn't Redis, but it was some kind of datastore) but the basic experience is what happened:
Q: Why does my webapp run on most of our tomcats but not this one?
A: That tomcat server has the redis client libraries on the classpath, so Spring Boot automatically started up a connection pool connecting to localhost on the default port, and automatically includes that in the healthcheck endpoint. Since redis isn't actually running there, it fails to connect, so the healthcheck always fails and the app never shows as running, even though your code is working fine.
Q: How do I turn that off?
A: Upgrade to the next version of Spring boot and then add this undocumented annotation to your configuration
Q: How was I supposed to figure any of that out?
A: Hahaha fuck you
Spring proper is fine, useful even. Spring boot is an absolute nightmare of COME FROM style incomprehensibility.
I've never used any other webstack (and I've used several, in several languages) where it was so hard to figure out why different tests pollute each other, causing subtle failures depending on the order of test execution. Sure, you could say that it's all a matter of learning the right way, but there are just a terrible amount of footguns.
Debugging why something doesn't work in Spring can also be a nightmare.
And the reference point for "compile-time safe framework" is of course not Django, which is written in a dynamically typed language...
> "...failures depending on the order of test execution."
Every time I've seen issues where the order of test execution matters, it's either been: (1) someone one writing integration tests and calling them unit tests, or (2) transaction management issues with H2 or some other embedded test database.
For #1, you're going to have a bad time with any stack. For #2, transaction management with Spring Data JPA is admittedly a tricky subject to learn. However, most of the time you can simply slap a "@Transactional" annotation on unit test methods that mutate the database, and that will cure what ails you.
> "Debugging why something doesn't work in Spring can also be a nightmare."
I don't know what could make Spring more challenging to troubleshoot than any other Java framework, other than diagnosing issues with complex dependency injection config. And that is often grossly overblown. Most of the time, you don't HAVE to use reflection-heavy tools like "@Profile" or "@ConditionalOnXXX". Keep it simple and there's really not much magic there. And IntelliJ or any other professional-grade IDE can help to manage the magic you do choose to employ.
> "the reference point for "compile-time safe framework" is of course not Django"
I'm simply saying that in any "X Stack Considered Harmful" discussion, eventually you have to put your cards on the table and disclose which stack you are comparing X to.
Everything is written in Rust here in the imaginary fantasy world of HN. But back in the land of the real, almost all line-of-business server side API development is written in either Java/JVM, Python, .NET, Node, or rarely some PHP or Ruby holdovers. Roughly in that order. Occasional oddballs here or there using Go or something else, but not common enough to make a dent and typically very difficult to evangelise.
So with that palette available to me, I'm going paint server-side microservices with Java and Spring virtually every time. My default comparison is to Python simply because it seems the greatest rival today in terms of adoption. But if we're looking at strongly-typed compiled languages only, in the business world in practice that limits you to Java/JVM or C#/CLR. Which is like saying that Coke is the bane of your existence and your company should be drinking Pepsi.
> I'm not sure what "no compile time safety in this stack" even means in the context of a strongly-typed compiled language.
Easy: as I said down below, you can actually get wildly different classes because of things like Hibernate proxies. Also, you guessed it, the dependency injection part. I just hate it. Nothing works together. We have so many hacks and weird work-arounds because something doesn't work. (Websockets not working with tomcat for example, or the many funky troubles with using two modes of authentication at once).
> Honestly, I've never run into anyone who considers Spring to be "the bane of their existence", where the real issue wasn't simply that the bulk of their experience was in something else. Where they weren't thrown into someone else's project, and resent working with decisions made by other people, but don't want to either dig in and learn the tech or else search for a new job where they get to make the choices on a greenfield project.
In my previous startup, we used python and flask. Something I don't deem scalable for bigger teams/apps. We love Kotlin and Gradle (especially multi-modules). But there are so many drawbacks that just suck time. I have a bunch of private projects, all in Go. Fast and efficient as heck. Nothing I'd like to scale beyond maybe 5 people or 20k loc tho (no idea if bazel or something could help with that, no experience). You get a lot of good stuff, but you gain in fragility with Java/Kotlin.
Another point that just comes to mind is: how unsecure is this thing even? Dependencies are ages old, requiring you to litter your gradle build files with work arounds and overwrites so you're secure from some (often critica) vulns.
1. Hibernate isn't even part of Spring. You're free to use a newer ORM like jOOQ if you like, or Spring Data JDBC if you want to get closer to the raw SQL. This gets to what I was saying earlier... usually when I encounter someone with strong opinions on Spring, they haven't really dug in to learn much about it. They don't know where "Java" ends and "Spring" begins, they're just winging it and don't like that this doesn't work out well with larger team projects (arguably with ANY stack).
2. I don't care which programming language or framework you are using. If you hate dependency injection as a general pattern, then every alternative I've ever seen boils down to either: (1) monkeypatching all over the place to achieve the same goals, or else (2) just static hardcoding everything and not writing unit tests with any mocks. I mean, plenty of people utilize one of those approaches. They just usually don't do so while discussing safety and security with a straight face.
2. You cite Python and Go as alternatives, yet immediately acknowledge that they're unsuitable beyond small teams or apps (my God, I'll take dependency issues with Maven Central over PyPI any day of the week!). Honestly, this whole sub-thread seems to boil down to you just preferring to work on small codebases over large codebases. And that's perfectly fine! I just don't think that's language or framework-dependent.
I never hated Spring, I got used to working with it. I found the annotation-based approach to configuring classes really opaque though, and it was hard to understand how to configure things because those were also magic annotations, and then some things were beans and others weren't and... ugh.
I don't fault Spring for it, and we were on Java 11 back then with just a little bit of new hotness. Java itself just didn't lend itself to the best ergonomics.
And you could fault Ruby or any dynamic language for the same, but they usually save you a little bit of overhead or boilerplate.
I'm always interested in hearing people's alternatives. Modern C# and ASP.NET is likewise annotation based. Most contemporary Python frameworks heavily leverage decorators, which is just another name for annotations. Decorators have made their way into Typescript. Rust code is chock full of attributes, and I've even seen Golang libraries simulate annotations with the `reflect` package.
This never really sounds like a Spring or Java thing. It always sounds like a "not liking dependency-injection as a general pattern" thing. My issue with that is two-fold: (1) people should just say that, and not tie it specifically to any one stack, and (2) the alternatives seem to be either monkeypatching or else writing untestable code, and those alternatives are hardly any better.
Dependency injection is fine, but it doesn't need to be as heavy as it is in Spring. There are lightweight and compile-time safe dependency-injection frameworks, and it's not even that hard to write your own custom dependency-injection logic.
I'm not saying annotations are bad though, I'm saying Spring's over-reliance on them makes the code harder to follow. Some of those annotations are actually done at compile-time via Lombok, which makes it even harder to know what the hell is happening.
Decorators and annotations are essentialy higher-order functions and higher-order classes, but there is nothing about them that makes it intuitive. They actually become quite difficult to write and much harder to debug, OOP's shit version of a monad.
I assume they mean problems with dependency injection.
If that's what they mean, I agree. I've seen dependency injection frameworks used in a bunch of different companies, and there are always people who consider it an essential lifesaver, like they just can't imagine working without it, and it always baffles me, because I've worked on equally large codebases that didn't use it, and it was occasionally a significant annoyance to pass dependencies by hand, but never equal to the annoyance of dealing with an automatic dependency injection framework.
This repeated experience of working with and without dependency injection, finding dependency injection to be at least as much hassle as it saves, and seeing that the people I've worked with who choose dependency injection have massively warped impressions of what it's like to work without it (they often think it's, like, impossible) has led me to see it as a tool that is driven mostly by FUD, at least at the scale of code that I have worked with.
And that's without even considering the deleterious effect that dependency injection has on design. In my cynical moments I think this is the real reason people love dependency injection. It's not that people hate the five minutes it takes to figure out how to manually pass a dependency to a module; it's that they hate the subconscious thinking that happens in those five minutes, as they see how the change affects the code, and it dawns on them that it's a code smell for every module to depend on everything else. Sometimes a dependency is a code smell, and dependency injection means you barely get a whiff, so faint you can pretend it's just your imagination. Doing it by hand means you get a few minutes to bask in the stench. You can't pretend you didn't notice.
Getting people to care about modularity, coupling, and cohesiveness in an application with dependency injection is markedly harder, just like it's harder in a language with global variables, just like it's harder to get people to think about APIs and modularity in a monolith than in microservices. And for me that's the worst part of working on codebases with pervasive dependency injection! Dependency injection is a massive liberating force for people who want to work without thinking about design. Instead of thinking about it, they just add another spaghetti dependency and keep on going, and do the same thing tomorrow, and the next day, and the next day. It's impossible to stop them! Adding a dependency is so immediate, so easy, there's no moment where they have to stop and think, "Hmmm, why am I using a dependency in module A that was only ever used in module B before? I'll need to instantiate it at the application level instead. But that means I need to pull some internal logic out of B so it can be run before B exists. Should I really be doing this? I'd better think/ask about this before I do it." Instead they just add an annotation and see if the dependency injection framework can figure it out. That's the only kind of problem I can think of that dependency injection excels at solving: problems that should never have been solved in the first place.
I like middle-ground dependency injection: do it, but only during build time.
Dagger library does that (generates all the glue code during pre-compile phase), so all the dependency-injection already happened before runtime. Also, JVM has an easier time reasoning/optimizing the code.
But I also found that writing the glue code by hand, same way as Dagger would do, is not that hard IMO.