Javalin – a simple web framework for Java and Kotlin
javalin.io
javalin.io
Seems right now everyone is under Spring's "boot", if you'll pardon the pun.
>Optimizing Enterprise Java for a Microservices Architecture
Captain Kirk:
When the spaceship nears colossal planet Enterprise, two kinds of developers on board will be revealed:
- those who jetpack enthusiastically down to the planet (their Buzzwordite (TM) foam resumes will softpad their landing)
- and those who retrorocket desperately away, seeking lean, mean, computing machines
Spock:
Logical flaw - you forgot off-by-one errors.
Yes, its a mess.
Javelin breaks free of that. Excellent stuff!
However, you’re right that Spring Boot has the lions share of the Java ecosystem.
Tho I’ve never used it and didn’t know how it compares.
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.
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.
Genuinely curious! Thx.
(edit: typo)
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.
[1] - https://jooby.io/
This definitely needs some supporting links. Jetty itself is extremely fast in my experience.
Also the URL parameter API is clumbersome:
Integer myValue = ctx.queryParamAsClass("value", Integer.class).getOrDefault(788) // validate value
I would prefer some specialized API like: int myValue = ctx.queryParamAsInt("value", 788);While it may not be as featureful as Springboot, it could generate swagger specs and had the features I needed at the time.
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.
Pick a DI library: Guice, Avaje Inject, Weld, countless others
Pick a configuration library: Avaje Config, Typesafe Config, Microprofile Config, countless others.
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.
1. https://github.com/manifold-systems/manifold/blob/master/man...
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.
Spark – A web micro framework for Java and Kotlin - https://news.ycombinator.com/item?id=39331126 - Feb 2024 (35 comments)
• 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!)
var obj = new SomeObjectWithHandlerMethods(dependency);
var app = Javalin.create().get("/", obj::handlerMethod).start();
Edit: For 4, Javalin 6 also has some documentation regarding Servlets here: https://javalin.io/documentation#adding-other-servlets-and-f...- 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.
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.
I'm very curious to see how that may look like. I've been using Django for years now and was never faced with such choice.
By definition, almost, compared to the bunch of libraries approach, whch is more flexible and composable.
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.
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?
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.
I've written a book about it here: https://frequal.com/Flavour/book.html
I often wonder why people always post these "sure, but what about..." replies. Hot take: There's more than one way to skin a cat, here's one.
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.
Logging in Java has always been a weird amalgam of interface libraries and implementation libraries with an XKCD'ish "14 competing standards" issue.