Hacker News new | ask | show | jobs
by MichaelMoser123 1613 days ago
I remember the days, when the Spring framework was advertised as a lightweight alternative to Enterprise java beans (ejb); now Spring outgrew the pretence of being lightweight, don't know when that happened. A year and a half ago, i got back to work with java and spring boot, and i was overwhelmed by the prevalence of annotations in spring boot.

To cope with all this, i wrote this little project: https://github.com/MoserMichael/ls-annotations

It's a decompiler that is listing all annotations, so it becomes easier to grep a text file in order to detect the dependencies between annotations.

it is using the asm library https://asm.ow2.io/ to inspect the bytecode files, so as to extract the class signature, along with the reference and declaration of annotations included in a classpath, or class files included within a directory structure. A limitation/feature is, that it is inspecting already compiled bytecode files.

8 comments

Indeed. I have a dozen or so microservices supported by team. Most are SpringBoot a couple of them I wrote myself with plain java and embedded tomcat. Needless to say Springboot stuff is rather complicated for such a simple business functionality. Errors are indecipherable being swamped by thousand line framework exception trace. But being an "enterprise standard" framework all projects must be move to this turd of a framework.
And in 99% of cases that little microservice will suddenly need thread pooling, logging, some more advanced db management or God help some random messaging service and you are back to re-implementing the myriad features of spring in a shittier way.

It is not an accident that things like ruby on rails are popular. These are well-tested toolboxes with a solution for almost every conceivable problem. There are exceptions where it is not needed, but for business applications those are not numerous.

I don't think people have any issues with the fact that spring is batteries included. It seems to me (and this is my personal experience too) that the large amounts of abstraction and indirection through annotations makes the code very hard to parse. It makes it hard to create a mental graph of how it all works together
My experience is that it's not only the usage of annotations, but the way Spring handles/implements those annotations which is confusing.

As an example, Micronaut[1] also uses annotations a lot, but their implementation is a lot easier to reason about, because there is less indirection with proxy objects and other weird stuff that Spring uses.

Micronaut does not implement nearly as many annotations as Spring though, which basically means less functionality pre-built. I'm not sure that's a bad thing, but it could be.

[1] https://micronaut.io/

I found the real challenge to be that it's very difficult - if not impossible - to determine how spring functions simply by reading code and using it. In simpler libraries or frameworks, I normally just read the source to understand how I should be using it. With Spring, I've had to spend a lot of time reading and re-reaading docs to understand what's going on.

I think that this is sometimes a hard shift for developers who otherwise have spent their lives with an ability to puzzle out the constructs that they come across.

I absolutely understand it, but I think the correct, although bit inconvenient approach is the one you mentioned — properly learning the framework either through docs or other materials.

Way too many developers try to write spring (but also jpa and many other useful, but complex tool) by trial and error, which let’s be honest, not a good tactic even if one can easily inspect the source. (The recently posted microsoft blog post “even if the precondition doesn’t do anything, you still have to call it” comes to mind)

You're, of course, right that you just need to study and learn a complex framework. Basically the same way that you learn a new programming language- they're all different and have different behaviors and idioms (pass-by-copy vs. pass-by-reference, etc).

However, there's another dimension here, and while it's not totally unique to Java, it's definitely present in larger magnitude in Java, in my experience. There are two parts:

1. None of these frameworks are 100% consistent. I haven't used Spring{,Boot} in years, but I can tell you that JPA/JDBC are full of little "surprises" and rough edges, like handling nullable database columns. If you are not careful with your annotations, you'll get a `0` value for your `Int` object field instead of the `null` that was in the database. You can then go for quite a while before you figure out that's what happened. Similarly, JacksonXML has all kinds of little gotchas when it comes to date-time types and timezones, primitives and null-ness, etc.

2. Most projects have more than one of these complex frameworks. See above. I listed JacksonXML and JPA/JDBC. Odds are that you have AT LEAST these three frameworks (including Spring) in your Java project, which means you have to study all three and learn all of their intricacies before being confident in the code you write. That's on top of learning how to write half-decent Java, which is hard enough with its type-erased generics, bug-prone null-handling, and very verbose class definition syntax. If it were just one thing, I'd be sympathetic and tell people to just RTFM. But, unless you plan on only writing Java code for the next decade+, I have come to believe that it's probably not worth it.

Looks like an IDE / language server that unrolls the annotation generated code might be what's missing here. I like my abstractions to be hidden, but I like to be able to peek under the hood. That's one of the problems of C++ templates, sometimes I want to look at the expanded code.

The GNAT Ada compiler has an option to output a much-simplified code. Not compilable Ada, but very inspectable unrolled, expanded code. Makes for a great teaching tool. Aaaaaaah this generic mechanism does that!

Edit: link https://docs.adacore.com/gnat_ugn-docs/html/gnat_ugn/gnat_ug... look up -gnatG[=nn]... Good stuff.

> Looks like an IDE / language server that unrolls the annotation generated code might be what's missing here.

I think you're missing the point.

Lucky me, there were libraries available, for mail, JMS, Kafka, logging and so on. Also implementing things in shittier way than Spring is difficult feat to achieve for my modest skillset.

However there were some impossibly complex requirement like thread pool and with great effort I was able to found a solution in standard JDK like :

`ExecutorService exec = Executors.newFixedThreadPool(st.threads);`

Spring could have greatly simplified this code I guess.

> It is not an accident that things like ruby on rails are popular.

Now if we could have that without the magic (neither from annotations nor from open classes), and with strong type safety and proper sum types... That'd be great!

> I have a dozen or so microservices supported by team.

Why do you need a dozen of microservices? Why not to use role-based monoliths? Why not to keep your "microservices" as independent modules, pack them as one app and let such app to configure itself with proper set of services and dependencies according to the config or CLI parameters?..

Just a developer there. So not calling shots on overall architecture. And yes we do not need probably 80% of that crap but not in position to make them see reason.
I'm in an enterprise and have successfully lobbied people to use anything other than Spring. Such organizations and teams while rare do exist so don't give up hope! (or just move to a more progressive org)
> I'm in an enterprise and have successfully lobbied people to use anything other than Spring.

I was in a team that used Spring Boot for a greenfield project. The documentation was great, there was tons of help of Stackoverflow (as it's Spring) and the consideration given to testing was first class. Deployment was also easy, as we just created a fat JAR. No application server necessary.

It was a great place to work.

I have also been in multiple organizations where Spring was used, including "modern" Spring Boot and greenfield projects, and people who knew every nook and cranny of Spring.

I don't agree with any of the things you bring up.

Spring documentation is and has always been poor and the sheer volume of outdated documentation (let alone ways to do the same thing) makes it needlessly difficult to find an answer to any given question.

Differences between the real app and real http requests to the app, and using the Spring test application context and Spring HTTP tests, result in tests misleadingly passing when things are actually broken. (or vice versa)

This is different to eg DropWizard where you actually boot the app (no different to how it does in a real env ie no "test application context") and make real http requests to it. (not some watered down fake Spring HTTP test requests)

Ability to deploy a standalone jar without the need for an application server is hardly unique to Spring.

Add in the horribly, horribly ingrained, unidiomatic ways people use Spring (eg sprinkling field autowiring all over the place instead of instead of using constructor injection) etc etc and every codebase is quickly completely ruined vs if it had been implemented in literally any other framework.

But! Fortunately for you, the Java community has made their choice, and it's not going to change - Spring is the default and correct option, and anyone who uses any other framework is just stubborn and wrong.

> Spring documentation is and has always been poor and the sheer volume of outdated documentation

Spring documentation is excellent. I had to learn Spring as a PHP developer, so I put the documentation onto a Kindle and read it. It's also versioned, so you don't need to read out of date versions:

https://docs.spring.io/spring-framework/docs/

> This is different to eg DropWizard where you actually boot the app (no different to how it does in a real env ie no "test application context") and make real http requests to it. (not some watered down fake Spring HTTP test requests)

Spring Boot allows you to write full application tests that will boot it up on a random port with the @SpringBootTest annotation, as is covered by the excellent documentation

https://spring.io/guides/gs/testing-web/

> Add in the horribly, horribly ingrained, unidiomatic ways people use Spring (eg sprinkling field autowiring all over the place instead of instead of using constructor injection)

You can use whichever.

> anyone who uses any other framework is just stubborn and wrong.

Not at all. Spring Boot is just a great solution, that's all. It's got strong support from a company, lots of documentation and first class support for testing. It also allows you to easily swap out different underlying technology, eg. you can switch from Jetty or Tomcat, Liquibase to Flyway.

It's a disservice to persuade businesses to use smaller projects that don't have a comparable level of support, or flexibility.

> It's a disservice to persuade businesses to use smaller projects that don't have a comparable level of support, or flexibility.

This is why we use Spring. We have confidence that it'll be supported long-term and will continue to have backing from a whole host of companies.

It's slightly different in a large enterprise environment where a service might be fairly straighforward - take in input, spit out output to some data store. But for the kind of work we do - end-to-end webapps - we'd be doing ourt clients a great disservice to sell them on a microframework with our homespun implementations of security, transactions etc. bolted on top.

> @SpringBootTest

Oh what do you know, yet another annotation...

I'm literally looking at the docs for @SpringBootTest

``` @SpringBootTest public class SmokeTest {

    @Autowired
    private HomeController controller;

    @Test
    public void contextLoads() throws Exception {
        ...
    }
} ```

This is case in point of what I'm saying - I highly doubt this actually spins up a full fledged app.

+ where is the controller coming from? It's not instantiated anywhere. What about it's dependencies? If I add one, this test will still compile, when in reality, the tests will all fail. (or at least, you would imagine most would) This field autoinjection approach encourages adding arbitrary dependencies with zero thought to anything - who cares, everything will just compile anyway. Meanwhile, a sane framework would force you to send in dependencies, forcing you to be smart about how to structure things and not compiling if you add a dependency to a controller but not to its test.

Also, what does @SpringBootTest actually do? How does it work? What if I need to do <anything that doesn't align with the simplest use case that the Spring devs cater to>? Who knows! "Just add the annotation, it's easy!" (but not simple - it's extremely, and extremely needlessly, complex)

> You can use whichever

Yes, and that is wrong. Even Spring devs themselves are now trying to put the field injection cat into the bag in favor of the correct constructor injection approach. They're not succeeding.

> Not at all

You say it's not wrong not to use Spring and... go on to say it's wrong to use anything but Spring. Classic Spring dev mentality, and completely disingenuously portraying things as if other frameworks like eg DropWizard isn't constituted of well maintained, well documented components. In reality, unlike Spring, frameworks like DropWizard are a collection of some of the best tools for each job, and it's both simple and easy, whereas Spring is just Spring, Spring and more Spring, and while it's easy, it's not simple- there's a lot of magic.

Anyway, we're not going to agree, and as I said, very close to 100% of Java devs and Java shops are 100% committed to Spring, so you win.

I'm just glad throughout my career I've found a few orgs who have been through the Spring grinder and realized the emperor really does have no clothes and have been open minded enough to look outside of the Spring bubble and try something else.

> Errors are indecipherable being swamped by thousand line framework exception trace

Don't you just look at the top lines?

If you're lucky and the exception happens in your code directly. If not you're gonna have about 30 lines of interceptors and generators and whatnot before you get to your class. If you call ClassA::foo from ClassB::bar you'll get about 5 lines of interceptors between foo and bar in your stack trace. Debugging is also a nightmare in IntelliJ as step-into and step-out will go through all those interceptors.
Debugging bothered me too. But in the end I have configured IntelliJ to skip all the interceptor classes while stepping into. Had to add a whole bunch of classes and packages for that though
I used Spring Boot and just set debug breakpoints, that way you don't have to step into and out-of, you just press "play" and it moves to the next breakpoint.
Another big difficulty is the handling of depedencies, as spring boot is bringing in gprc, jpa, jdbc and countless other libraries. One really needs a dedicated team to figure out all these issues!
Very true! But management is sold on "best practices" from VMWare suits. So any practical difficulty is just an excuse for not learning the latest, Next generation technologies.
It brings in whatever you use. Also, it has a goddamn webpage where you can click together what do you want to use and it will create an initial project for you with the chosen build tool and what not. It hardly gets easier than that.
This isn't true. Spring Boot by itself brings in very little, you can however _add_ GRPC, JPA and JDBC support by adding a library and Spring Boot will even autoconfigure it for you.
IBM WebSphere Application Server took several minutes to start or stop. Deploying war file took another 10-30 seconds. And you had to restart application server sometimes.

Spring Boot application with few controllers starts in 2 seconds on my outdated laptop.

Spring is lightweight, compared to old tech.

Not the most lightweight, that's for sure. Simple Java web server which uses socket API to server requests, starts in few milliseconds. That's the bar.

Spring Boot is not the same thing as Spring, and is indeed recapitulating all of the mistakes of EJB (I guess it's been long enough that the new generation of developers doesn't know about the problems). You can still use vanilla Spring though.
> Spring Boot is not the same thing as Spring

Spring Boot is an opinionated way to configure Spring applications.

> recapitulating all of the mistakes of EJB

Which mistakes is it repeating?

> Spring Boot is an opinionated way to configure Spring applications.

If by "configure" you mean "make arbitrary changes to the behaviour of". Spring Boot adds a bunch of spring-boot-specific stuff that can't be replicated in vanilla Spring and isn't supported for use outside Spring Boot (e.g. @ConditionalOnMissingBean and friends are explicitly not supposed to be used in non-boot Spring configurations). This is a long way away from just sugar for an ordinary Spring configuration.

> Which mistakes is it repeating?

- Huge and incomprehensible

- Components can refer to other components in ways that are completely invisible in the code

- No way to understand your application's behaviour by just looking at your code, because it can vary drastically depending on the things that are instantiated by the container at runtime. (In EJB this was container-provided services, in Spring Boot it's configurations that are automatically instantiated if they're present on the classpath, without the application ever referring to them at all)

- In practice applications depend on implementation details of the framework and cannot safely upgrade or migrate

Spring Boot has remoting like EJB did??
Well, Spring has multiple fundamental problems. They've chosen wrong language and wrong techniques. Runtime metaprogramming is sick and slow.

Though that doesn't mean that anything is wrong with JVM as a platofrm.

spring-core can be replaced by, essentially, several hundred lines of LoC: https://izumi.7mind.io/distage/index.html And in fact these lines can do lot more than Spring.

It is always like that, eventually the revolutionaries become the government they set out to replace and the wheel of time turns again.
Annotations are a band aid. They easily move what otherwise were compile time errors into runtime errors. The advantage of using them is that you have to write less (repetitive) code.

I prefer code without them. They add magic. I dont like magic in my code.

I honestly think the big issue here is using Java for these use-cases. I know that sounds flame-baity, but I'm being sincere.

Java is a very primitive language. For the vast majority of its life, it's basically been C + basic classes + garbage collection.

As a result, it's very verbose, which is totally fine for a low-level language. But, when building large, high-level, business apps, it's just a weird fit. I think that's why we see all of these annotation-heavy band-aids on top of Java (Lombok, Spring, JPA, etc)- it's because Java is actually not the right tool for the job, but instead of migrating (or inventing) a better tool, we just sunken-cost-fallacy ourselves to death.

I disagree here. Having GC, VM, streams, big stdlib, makes is quite highlevel.

It's not very terse (like Ruby maybe), but modern Java is terse enough.

To keep a language small is a good thing: less to remember, easier to join the team. Go, Elm, Reason/ReScript, LISPs all go that route.

Java misses some things badly. Like being able to have a reference to a method (Jodd has a library fix for this). Or like sum types and pattern matching.

But I'm more bitten by features that Java has than what it has not. Overuse of Exceptions (instead of sum types) and Annotations are my biggest pains.

You see a lot of Java's shortcomings properly being addressed in Kotlin. Like the getter/setter story. And "standards" like Bean and XML config have given Java a bad rep.

> I disagree here. Having GC, VM, streams, big stdlib, makes is quite highlevel.

I used to think that, too. I probably wont't convince you otherwise, and it really doesn't matter how you or I categorize the language, but I think a solid argument can be made that Java's abstraction power is almost zero, especially if you consider versions older than two or three years (before records, switch expressions, sealed classes, etc). I also think that the ability to differentiate between boxed and unboxed primitives, no concept of immutability/const, primitive synchronization tools like mutexes and raw threads, etc, make a compelling case that Java is not well-suited for thinking at a high level of abstraction.

Think about how much code it required in Java to create a value type with four fields before records. You needed to list all four fields and their types in the class body, then you need to list all four fields and their types in the constructor signature, then you need to write `this.foo = foo` four times in the constructor body. Then, depending on your conventions and preferences on mutability, etc, you'll need to write getters and/or setters for the four fields. Then you need to write a custom `equals()` implementation. Then you need to write a custom `hashCode()` implementation. Then you need to write a custom `toString()` implementation.

I hope you don't have to update that class, either, because forgetting to change your `equals`, `hashCode`, and `toString` will cause bugs.

There's basically no universe where you can convince me that this shouldn't be considered low-level programming.

> To keep a language small is a good thing: less to remember, easier to join the team. Go, Elm, Reason/ReScript, LISPs all go that route.

I agree! Small/simple is great. But look at how expressive Reason/ReScript/OCaml is/are compared to Java. Same with LISP. They aren't huge languages with endless features being added on all the time, but they allow for much more high-level programming than Java, IMO.

> Java misses some things badly. Like being able to have a reference to a method (Jodd has a library fix for this). Or like sum types and pattern matching.

To be fair, though, this is not what Java was designed for. Java was initially an object-oriented language. Sum types and pattern matching are not OO. Object-orientation was supposed to be about black-boxes sending "messages" to each other while maintaining their own internal state and invariants. In "true" OOP, you wouldn't have a sum type, because you'd have an object that would exhibit different behavior depending on the implementation.

Granted, we're moving away from hardcore OOP as an industry (thank goodness). But, I'd argue that the "problem" isn't Java's lack of sum types and pattern matching, but rather that we're trying to make Java into something it isn't. We should just use a different tool. I'm not in my garage trying to attach a weight to the end of my screwdriver to make it better at driving nails- I'm going to my toolbox to grab a hammer, instead.

Agreed with many points. So Java is then somewhat in the middle.

OTOH lets consider Rust. It is in my book a low-level lang, close to the metal (hence Rust?). It has a muuuuuuch better feature set compared to Java (IMHO). But it is geared at low-level, so no VM and certainly no GC out-of-the-box... In your def Rust'd be a high level lang: which is cool. I like your def :) But I still def'd high level slightly different: more in terms of the ability program close to the machine, or more in abstractions.

> Sum types and pattern matching are not OO.

Traditionally not often found in OO, but otherwise verrry much compatible with OO.

> In "true" OOP, you wouldn't have a sum type, because you'd have an object that would exhibit different behavior depending on the implementation.

I think this is more about tradition than "trueness". I cannot return an Either<Error, Result> from Java. That sucks. Many have used Exceptions to fix it, but that suck even more. I'd say OO is compatible with sum types.

> But, I'd argue that the "problem" isn't Java's lack of sum types and pattern matching, but rather that we're trying to make Java into something it isn't.

This always happens. And some langs are better suited for that than others. I work on a Java codebase currently and welcome those features, and actively consider moving the whole show over to Kotlin. Kotlin to me is like a typed Ruby. And in Ruby many libs are in C (in Kotlin then many libs'd be in Java).

I think OO and FP bite eachother. You cannot have both. See Scala. It becomes way too big as a language, and lack idiomatic ways of doing things. But one can have a lot of FP in an otherwise OO lang (see Kotlin for instance).

> OTOH lets consider Rust. It is in my book a low-level lang, close to the metal (hence Rust?). It has a muuuuuuch better feature set compared to Java (IMHO). But it is geared at low-level, so no VM and certainly no GC out-of-the-box... In your def Rust'd be a high level lang: which is cool. I like your def :) But I still def'd high level slightly different: more in terms of the ability program close to the machine, or more in abstractions.

I'm not a super clever person, but I once made a quip that I was pretty proud of, and I've repeated it online a few times:

"Rust is the highest level low-level language I've ever used. Java is the lowest level high-level language I've ever used."

Of course, the hardest part of all of these discussions is agreeing on what the words we're using actually mean. So what does "high level" and "low level" mean when it comes to programming languages? Are they mutually exclusive, or can a language be both? Is there such a thing as a "middle level"?

I don't have a great objective definition. Basically, I see "low level" approximately meaning "I have to think a lot about computery shit" and "high level" as "My code mostly looks like domain logic". There's a lot of wiggle room in there, for sure.

But I'm curious to challenge you more on what you mean by "close to the metal." Is being close to the metal somehow about abstraction-ability of the language, or is it a euphemism for some languages just being inefficient with computing resources? And, specifically, the things that make Rust closer to the metal than Java. I think the "obvious" answer is that Java runs in a virtual machine and has garbage collection, whereas Rust has neither of those. But I'm going to push back on those "obvious" high-level features.

First of all, Java-the-language has no idea that it's running in a virtual machine. I could, hypothetically, write a compiler for Rust that spits out JVM bytecode- would that make it a high-level language? Probably not.

As for garbage collection, I'd agree that compared to manually allocating and de-allocating memory space, garbage collection certainly allows us to think in a higher level of abstraction by letting us ignore details about how and when our data come to exist in our program. But (safe) Rust's approach to memory allocation is pretty far from manually allocating and de-allocating blocks of memory from the OS (which is basically a VM, itself, isn't it?). Rust largely allows me to ignore how much memory I might need for a String or a vector of data.

Now, it would be crazy for me to claim that Rust's memory model is as high-level as something with a garbage collector. After all, in Rust we have to think about borrows, Sized vs. unsized types, and sometimes have to actively think about lifetimes.

But, I will claim that Rust's memory management is still a big step up the ladder of abstraction from C. So, if being "close to the metal" is about needing to think less about nitty-gritty computer stuff, then Rust isn't as close to the metal as our gut instinct might say it is.

On the other hand, in Java, I still need to think about specific computery stuff when choosing between boxed and unboxed primitive types. I need to think about bits and bytes when choosing Short vs Int vs Long, rather than having a default Integer type that can be arbitrarily big or small. I need to think about mutexes and threads and thread-pools for concurrency/parallelization. I need to worry about stack overflows. That's all true of Rust, too, of course, but my point is that both of them require putting a lot of thought into non-domain concepts while programming.

Java did recently get records and sum types, so its abstraction ability has gone up substantially from where it was just a few years ago.

Rust has async/await for concurrency that can even be used in single-threaded contexts. Java doesn't even have that yet.

Rust has type classes. Java does not.

Rust has easy-to-implement newtypes. Java does not.

Rust has (im)mutability as a language concept. Java does not.

Rust has data copying as a language concept that actually works. Java has Clone.

Rust has hygienic macros that can be used to extend the language, create DSLs, and reduce boilerplate. Java has annotations that can be used to reduce boilerplate- mostly with a runtime cost and runtime errors.

So, which language is more capable of higher levels of abstraction? Honestly, it's probably Rust. Which language requires you to think more about memory stuff? Rust- but I think it's less of a lead over Java than most people would guess.

Which is closer to the metal? I don't know. Rust runs faster.

> Traditionally not often found in OO, but otherwise verrry much compatible with OO. > I think this is more about tradition than "trueness". [snip] I'd say OO is compatible with sum types.

We'll probably have to agree to disagree. Of course sum types can exist in a language that touts itself as OO, but using them extensively is just not OO. It's literally inside-out from OO. If you look at a language like SmallTalk, even True and False are objects, and there are no if-statements. Rather, True and False are both sub-types of Boolean, and Boolean requires the methods ifTrue and ifFalse. True implements ifTrue to perform any action that was sent as a parameter, and implements ifFalse as a no-op. You can imagine False's implementations. So, some object sends you a Boolean and you call the ifTrue method of the Boolean with an action to be performed if the Boolean feels like it (it feels like it when it's a True :p). In true/extreme/hardcore/pure OOP, you wouldn't even have or use if-statements when implementing logic- it's polymorphism all the way down.

Is that useful or practical? I don't think so. But that's why I claim that sum types are not OO. I also claim that if-statements aren't really OO. And boolean is a sum-type.

> I cannot return an Either<Error, Result> from Java. That sucks. Many have used Exceptions to fix it, but that suck even more.

Yes you can. Java now has sum-types anyway, but you could always implement a generic class that could be in either one of two states with whatever methods you need to check its state and extract the data. It's awkward and janky, but this is Java- what isn't awkward and janky?

> I think OO and FP bite eachother. You cannot have both. See Scala. It becomes way too big as a language, and lack idiomatic ways of doing things. But one can have a lot of FP in an otherwise OO lang (see Kotlin for instance).

I agree with the first premise, but I disagree that Kotlin has successfully added FP stuff to an OO language. I think that Scala has done a much better job of being FP and OO, actually.

Java has had method references for almost a decade. Pattern matching also was recently released.
> Java has had method references for almost a decade.

So how can I pass a method to another method? (without using lambdas)

> Pattern matching also was recently released.

I know, great improvement (if it comes together with proper sum types). One less reaosn to switch to Kotlin.

someObj.someMethod(Some::reference);
the pernicious thing about spring is there appears to be 15 different ways to do the same thing. Everyone's idea and enhancement request was thrown in. Plus things got left in that should have been deprecated after better ideas came along or the java language improved to allow new techniques.

I'm glad someone is working on a light weight replacement to spring. I had some ideas on a light weight DI framework but never got around to it.

> the pernicious thing about spring is there appears to be 15 different ways to do the same thing.

Oh gods below this. I was half wondering if I was writing Perl with how much TMTOWTDI was floating around in the cesspool of Lombok and Spring.

I use http://sparkjava.com in my hobby project. It mostly does what I want, but I had to hack it a bit to be able to stream responses. It's also crazy fast and about as lightweight as these things get.