Years ago when I was investigating Spring and Java EE, I was taken aback by how much additional work was involved. Not only do some tutorials only show how to install through Java's dedicated IDE, but also deciding on which application server to use, which adds another layer of technical decisions and slows down the process of "quickly testing" frameworks.
If you were "deciding which application server to use", then that WAS "years ago" indeed! The paradigm shifted, from applications being deployed to a server such as Weblogic or Websphere, to applications bundling their own HTTP server like Node or Golang. This shift happened around 15 years ago. Modern Java development looks almost nothing like it did back in that era.
@SpringBootApplication was all you needed on top of a main class. The biggest issue is somewhere along the Spring -> Pivotal -> VMWare -> Spring hot potato, their tutorials and documentation fell off a cliff. Everything became “just use initializer…”
It's either Spring, or Java EE's way (together with the new Jakarta EE paradigm, which is actually pretty cool if you ask me). I've seen the latter a lot more in the enterprise sphere.
Feels lightweight but powerful - it seems to cover everything I need from a framework in terms of URL routing and request/response handling, with minimal cruft and boilerplate.
That was why I picked it awhile back. I hate that most Java development these days has a large component that deals with writing XML. It allowed me to focus on the code I needed to write and not figure out what parts I needed to cobble together.
I groan every time I come across a Maven project, which AFAIK isn't quite yet "legacy."
What about dependecy management, testing, building, and all of the other non-java-code bits? Gradle only goes so far in my limited experience.
The non-Java parts of Java development are what repeatedly get me to choose Go, Ruby, Python, TypeScript/node.js, etc. The tooling is magnitudes easier to use.
Not who you are replying to but I would define legacy as any project that is doing spring di and wiring the project together with XML (excluding Pom for maven building), rather than annotations or raw Java code. Or anyone still deploying to an application server.
But the project configuration has nothing to do with the web framework. You can use Javalin with Maven (and therefore XML), too. And you can use Spring without any XML by configuring the project with Gradle's Kotlin DSL. Modern Java development is not what it was 20 years ago.
One of the good things about it is that using asynchrony is optional. If you don't have to call out anywhere to build the response, processing can all stay in the handler's calling thread. If you do, you can return a future and have the library handle the async for you.
One downside is that it is based on Jetty which isn't considered the most performant backend. A lib with a similar API but based on Netty is Jooby [1] which scores well in the Techempower benchmarks.
I used Javalin for a startup I helped with a few years back. I found It much easier and lightweight to turn up REST endpoints compared to Springboot/etc.
While it may not be as featureful as Springboot, it could generate swagger specs and had the features I needed at the time.
In my experience the web layer is (or should be) pretty thin, so a framework in this layer shouldn't change too much in the overall application architecture.
Springboot's dependency injection, auto configuration and yaml configuration system is a value proposition that I find hard to beat. I find it's fairly effortless to build almost any kind of backend software, and if I want something "lightweight" I probably wouldn't use java in the first place.
While that's my pragmatic view, to be less critical, I am glad to see a project that provides a robust and elegant API for building rest services in Java.
I actually preferred the XML config, and I hate XML. Why? It was all in one spot, not spread out among the source files. It could be "deployed" alongside the code and not have to recompile/build. This was a bigger deal back then when "config" was often outside the official "installation" procedures that required 5 layers of OK's to put a new "compiled version" of an application in a production system. Not saying that was the right way to do it, but it existed.
It's actually not that hard to beat. Quarkus proved that it's way better than Spring Boot in a lot of aspects based on open standards and battle tested frameworks like Vertx. Also on Quarkus you can use in parallel blocking and non-blocking IO and also high level declarative routes with annotations to lower lever vertx routes and handlers.
The rubegoldbergery around DI is amazing. You don't really need a DI library in many cases. A single Java SPI supplying access to all interface dependencies works extremely well. You provide a default implementation consumers can override using basic SPI mechanics. Done.
> I am glad to see a project that provides a robust and elegant API for building rest services in Java.
Alternatives for _consuming_ REST services is perhaps an equally interesting proposition, particularly since there are potentially many consumers per service e.g., the manifold JSON project[1].
Other areas where Springboot feels overbearing include JPA/ORM support. In my view this is its greatest weakness, but like the "no one ever got fired for buying IBM" cliche, the same can be said today about Springboot.
I find it interesting to see how would something like Javalin work if coupled with Spring DI. Throw the Spring web stuff out and use Javalin instead. Spring web always felt archaic to configure when you want to go beyond simple CRUD operations like implementing sophisticated RBAC.
Nowadays I prefer starting with a microprofile subset consisting of jersey+jetty offering the standard JAX-RS APIs.
Is really lightweight at only 6MiBs shaded and grows opportunistically into the microprofile stack of quarkus etc. - you can even integrate the jax-rs bits into spring if you want.
Microprofile has a bunch of standards compliant implementations, so you aren't locked into a specific implementation project.
I think the option is overlooked because the landscape is complex and this setup doesn't have a marketing name, but it is very stable and polished.
You also get IDE support, because JAX-RS is part of microprofile, and formerly part of JavaEE.
Wow, this is a blast from the past for me - I was examining the framework in 2015. Amazing to see it's still well and alive. Back then, I'd simply use some resteasy framework.
If you like lightweight libraries, check out Helidon SE 4. It's built on Java 21 virtual threads, embraces blocking paradigm, built by Oracle, fully open source, of course, comes with some batteries included. Worth checking out, IMO.
Personally, I rather enjoyed Dropwizard, because it had a bunch of idiomatic Java packages that most developers would have already seen elsewhere, all put together in a way that doesn't seem to have too much boilerplate:
My API SaaS company chose Javalin for our API backend to start with, for several reasons:
• it is "just enough" web framework for our needs, without all the bloat that a framework targeting browsers/serving HTML would have;
• it has an annotation-based OpenAPI auto-summarization plugin, to allow us to immediately get some docs and autogenerated SDKs up;
• and it explicitly owns the lifecycle for its components — using e.g. an embedded Jetty instance that gets started + configured in code by the framework — where you can inject config into that code, or replace it wholesale. (This is what we were used to in other web frameworks, and is unusual in Java land, where web servers seem to be separate "background services" that happen to start up independently in the same JVM, and then auto-discover and runtime-bind to your web service's "background service" through XML files the framework generates and the web server probes for. Which is a bit much when you're not running an enterprise-y "app server" with tons of different servlets mounted to it that can be hot-upgraded without restarting the server, but instead a single-purpose Java app in a Docker container as a k8s Deployment.)
That being said, as our company grew, we eventually ran into the disadvantages:
1. Scanty documentation.
The https://javalin.io/documentation page is all you get. Want, say, a list of the HTTPResponse exceptions Javalin offers? Or to know the types — rather than just the behaviors — of the getters and setters on Context? Well, too bad — go read the (Kotlin) source code!
(Why no per-module JavaDoc docs? Even the hollowed-out docs that just give constants + methods, that you get by generating docs from a module with no docstrings, would be better than no JavaDoc! IDEs do give you some of the same info as a trivial Javadoc, but JavaDoc docs are a lot more browsable as a reference when you don't know the name of the thing you want, just the type.)
2. Static routes!
Javalin routes must be bound to static methods of Java classes. You can't† build an object, pass the object to your Javalin app constructor, and have it bind routes to that object's methods. Which makes constructing any kind of DDD service contexts — and having them be testable, with separate e.g. DB providers in a test context — near-impossible. Javalin and dynamic Dependency Injection — the kind you do in tests — just don't get along.
† Or, well, you can do that — in that you can bind each route to a closure which calls the method on the object. But those closures need to be able to bubble arbitrary checked exceptions back to the Javalin exception handler (which the trivial examples in the Javalin docs carefully avoid the need for by only calling pure code.) Which means so you can't use the syntax-sugar'ed kind of functional closure. So each route balloons into 10 lines of instance-subclass boilerplate, and your routes module is no longer a readable + greppable DSL definition of "what your app reacts to", but instead a big mess.
3. No support for writing Java Servlet middleware.
Javalin uses servlets under the covers, but doesn't expose the servlet "stack" for injection. Instead, you have to write Javalin plugins (Which haven't been good for much for most of Javalin's lifetime — though they seem to be pretty flexible now, so this might not be as much of a concern if you're doing a greenfield Javalin project today.)
I'm guessing that most devs still just end up just dumping all their middleware-ish concerns (auth, rate-limiting, etc) in app.before + app.after. Which thereby accidentally leaks resources — because some exceptions can cause app.after to not run! (You have to do a little dance with app.after and app.exception calling into common code — essentially "rewriting the framework to do the thing it should have done" — to resolve that.)
4. The Javalin devs are slow to add very critical features that tons of people ask for, sometimes leaving outstanding requests on the backburner for years.
Until Javalin 6, there was no app.beforeMatched, and therefore, no request-lifecycle callback point for "after routing has been resolved, but before actually calling the route."
So, before recently, you couldn't do rate-limiting or auth or etc generically in middleware, while also taking the static route-name of the resolved endpoint — the string that looks like "/account/:id/binding" that the Javalin context gives you access to inside your controller code — into account as an input. Instead, to do this, you had to let the route trigger, and then call generic code from inside the controller (see e.g. https://javalin.io/plugins/how-to#creating-a-plugin-with-con..., which seems to show that the Javalin devs still think of this as the idiomatic way to do this.)
Even with this particular feature request finally resolved, there are still several outstanding ones. And given the shape of Javalin as a framework, the way to work around these missing features, always ends up involving writing redundant generic code inside your MVC controllers. If you do this enough times — and you follow the natural desire to factor them out — then you end up basically building a framework on top of your framework! (And then, when these new features do finally come along, you're already added all this now-unneeded architecture that's very hard to get rid of.)
---
A while ago, we got fed up with the disadvantages — so we're very likely going to be moving to Spring Boot.
At some point. (Given the particular shape Javalin forces your code into, though, such a move is a lot of work!)
Strange — I swear this wasn't the case. I and several other devs tried for a good three weeks to get Javalin to accept such route bindings. This was a year or two ago, though. Maybe the interface type for a handler function was changed in some more recent version of Javalin?
Not sure about the older versions of Javalin, but from my experience with v5+ it just uses regular Single Abstract Method conversion for the Handler interface, which has a single method with just one parameter (the Context).
The advantages of microframeworks (e.g. Sinatra, Flask, Javelin, etc) are that as your needs expand, you can bolt-on the additional functionality as you see fit, from a "best of breed" selection of your favorite libraries.
The advantages of batteries-included frameworks (e.g. Rails, Django, Spring, etc) are that your application is easier to find people to work on, and has a greater change of remaining coherent and maintainable for longer.
In the real world with microframeworks, some rock star starts a greenfield project, and has a delightful fun time selecting their personal picks of various community libraries to bolt functionality on top of the microframework. Then that original developer moves on 6 months later. The best-case scenario is that half of their pet community libraries are no longer being maintained 12 months after that, and the team has to constantly sink time into migrating pieces of the app to other actively maintained libraries. The worst-case scenerio is that you get the next-generation rock star, who wastes time ripping out pieces for no real reason other than using their own personal pet favorites.
In my experience, the microframeworks are ideal for apps where you can be fairly confident that the scope will never expand (e.g. simple Lambda functions). Otherwise, they're popular toys for startups, and nooks and crannies of large enterprises where no one's really providing any oversight over the juniors.
Is the main advantage of frameworks then a social thing? You can depend on the future development of the framework as a whole, as opposed to a bunch of smaller libraries, so it doesn't matter if the sum total quality of those smaller libraries is greater than the framework.
Does it also have the advantage of not having to remember to import things? Like, does Spring prevent CSRF attacks etc. out of the box, and it's very difficult to accidentally create a vulnerability using it?
Big factor in rise of frameworks is outsourcing the architecture to it.
Lots of devs need the prebaked structure. It is honestly helpful in many cases, but can also be a frustrating maze at times to find the hook/extension point you need.
Sure? I mean any framework will constrain you to what it provides. These frameworks usually include some set of functionalities:
- an underlying service to listen for http/https requests (eg: "web server")
- easily map inputs to code/services ("routing"); often also providing helper functionality to extract data from paths, map and convert JSON (etc) to domain objects
- some way to inject your data into a return "view" (json, html, xml, ???). Might also include reusable view code snippets (eg: "partials"), and/or some "design pattern" (eg: "layouts") system
- an ORM or other db-layer abstraction
- some background job processing abstraction
That's about all you need; the extent to which the framework simplifies doing those things is akin to the amount of constraints. Everything else is gravy.
Do note that "big" frameworks that provide ALL of that plus more can often be harder to work with since you might be forced to learn more than you need, and it's overwhelming.
There's also the concern of modularity... if I don't like their ORM, can I use my own? How hard is it to add functionality not provided, etc.
Totally agree. The modularity and the amount of “batteries included” can eventually become a negative. The last thing I want is for someone to reinvent RxJava yet it seems to happen in every one of the more bloated frameworks. “Are you familiar with reactive programming? Oh cool - but are you familiar with mixing RxJava and Spring’s reactor? That’s what we now have and it’s a real treat sometimes.” This is just one sad and possibly poorly outdated example as I haven’t worked with spring in some years now.
I worked in a place using Django, with dozens of additional plugins and enhancements.
And my PR's were routinely flagged for writing 3-5 lines of idiomatic python that any python programmer (indeed, any PROGRAMMER) would have understood instead of spending half a day to try and find the "Django way" (or worse, the "whatever 5000 line plugin we installed to save the programmer from having to repeat 2 lines of idiomatic python 10-12 times") way to do it.
Their (IMO insane) adherence to the common, but misunderstood idea of DRY was insanely unproductive.
These frameworks become "languages" in themselves, and unless you're in it all day every day, it's _harder_ to get things done due to their incredibly large attack surface.
It was over 3 years ago so sadly I can't. If I recall correctly a number of things I was forced to put into some sort of serializer. I'm also trying to remember what the plugin/enhancement was involved; "Django Rest", maybe?
I saw that it was already being mentioned in this thread. I have tried out both Javalin and Ktor recently but as a beginner I was lost as to what I should choose and why. Given that this thread itself was probably created due to this[0] thread about Spark, I think it is on topic to discuss it here. I will admit, I could have written more than just "what about..", that is my mistake.
1. The post is already about an alternative to the mainstream (Spring), so adding another alternative seems perfectly valid.
2. I know neither Spring nor this alternative, but want to add the one framework I know of. This gets increasingly popular after previous 1./2. responses.
3. My/my company's badly tested pet library, let me show you it.
I don't like that the logging just happens implicitly? Like, it asks to install slf4j... But can't I just start out with the standard kotlin logger before installing another dependency. That part imo is a little underdocumented.
Seems right now everyone is under Spring's "boot", if you'll pardon the pun.