Professionally, I work on adding business value by using software. Luckily I managed to transform my hobby into a job and occasionally still enjoy writing software and building things in my spare time. Sometimes those "things" are quite silly, like a website called this button does nothing.
As the name implies, it features a button which does nothing except count how many times it has been pressed. It has some interactive elements such as a high score and a live stream of who else is pressing the button. Somehow, on average 10 unique users per day indulge on some useless button clicking with occasionally people (or automated scripts) sticking around and reaching scores in the thousands.
The project was written a long time ago and put together in a few hours in a weekend. The backend was a simple PHP application and MySQL data source. Instead of AJAX-polling the server for updating the front-end, the PHP application communicated via Redis Pub/Sub with a NodeJS server which then streams events over a websocket to all connected clients. The main use case of the application worked something like this:
Of course there is a myriad of ways to achieve this kind of functionality and this "architecture" (if you can even call it that) is certainly not the best way. Silly weekend projects are however silly for a reason and as I already build Enterprise Grade Software™ during business hours these kind of projects serve well for diving into something new or incorporating techniques you wouldn't typically use. The concept of a "jam session" as a musician or "doodling" as an artist comes close.
The good, the bad, the ugly
Proficiency in a tech stack enables quick development. While front-end work is not my specialty, VanillaJS simplifies interactions with http servers, websockets, and the DOM without convoluted build processes. When combined with PHP, it facilitates rapid prototyping. I have been wanting to transition the back-end away from this tech stack as my professional focus now mostly revolves around the JVM.
The old application had a lot of moving parts. Not included in the diagram is nginx which served as a reverse proxy for the front-end, PHP and node.js. Theoretically, all parts could be containerized and scaled horizontally but would still require maintenance, integration and knowledge of various different subsystems. In practice, this all ran on a single $4 VPS. Being a sysadmin for a VPS is not something I particularly enjoy.
However, this is not a mission critical application and just something I tinker with occasionally. In the grand scheme of things it doesn't really matter how it runs and thus no load or performance tests were ever performed. Why would you for a silly weekend project which averages around 10 users?! You could rebuild this in whatever esoteric language or framework with an Excel data source and it would still work.
Quarkus 3 released
Much time has passed and "this button does nothing" is still living up to its name. It would hum along for another long time if I didn't come across the recent Quarkus 3 release. As I'm primarily working with the "Spring umbrella", Quarkus is one of those things overheard at tech talks and read about online but I've never used it. Some of its popularity can be attributed to Red Hat's excellent developer PR and marketing ($$$) but there's also organic buzz and appreciation in the Java community.
Performing the migration is straight forward. Begin with a Quarkus starter application in dev mode, point the front-end towards it and start adding the functionality to make it work again. No functional changes were made to the front-end so it's mostly just a back-end migration. After adding a few Quarkus extensions, it took surprisingly little effort and in no time the migration resulted in something like this:
For brevity the details are omitted but basically a http endpoint consumes a button press, updates the data attached to the current session and puts a message on the event bus. A worker thread consumes the message and notifies all clients connected to the websocket that there is a new press and as a result the front-end UI is updated. Not illustrated are a few scheduled tasks which cache recurring queries such as the latest events and high scores.
Native container support, out of the box
With Spring Boot, making an executable JAR is easy and containerizing it is not very difficult. With Spring Boot 3, native executable support with GraalVM has also become easier and no longer experimental.
Quarkus however has been leading with container & native support out of the box and not as an afterthought baked in later. This is beneficial for cloud environments due to cost savings (less resources) and more performant deployments (+ scaling) due to fast startup times. With extra work you can get similar results with Spring but the laser focus on "Kubernetes-native development" does make a difference.
The integration tests can also be run against the native executable with a single annotation so locally you run tests against a very production-like executable. Packaging a containerized native executable is one command and basically zero configuration if you stick to the defaults:
As mentioned earlier, the entire application used to run on a VPS. After migrating to Quarkus and copying the existing mysql database to a postgres instance it's now deployed on fly.io as a native GraalVM executable living in a container. That includes the front-end which are just a few static resources served by Quarkus. No more sysadmin hassles! (Now it's called DevOps.)
It's not Spring but... familiar
For someone used to Spring Boot and the Spring Framework, getting up to speed in Quarkus will not take a lot of effort and time. Quarkus 3 and Spring Boot 3 (Spring Framework 6) both use Java 17+ and the Jakarta EE 9+ spec. In general, concepts overlap but the mission of both frameworks are different. Spring is a general purpose framework whereas Quarkus explicitly intends to be container-first, developer friendly and highly performant.
One thing I noticed is that Quarkus seems to be much more focused on being standard-compliant with many extensions using an implementation of a Jakarta EE spec (for example JAX-RS, Jakarta Websocket). With Spring you either have limited spec support or it's much more common to use the Spring sauced variety because it has some quirks or additional features incompatible with the spec. For example: Spring MVC is more popular than the JAX-RS compliant extension and DI is typically achieved with Spring stereotype annotations instead of the Jakarta CDI annotations (which Spring also has some support for).
The documentation is sparse but good
With Spring everything under the Spring umbrella (Boot, MVC, Data, etc) has their own reference documentation. If you can't find it there, there's guides which can get you up to steam. After that, you can Google whatever topic you want to know more about and find hundreds of articles, stackoverflow questions and even a few books tailored to very specific Spring subjects. Some of these resources might be outdated but generally you'll find something to point you in the right direction fairly quickly.
Quarkus doesn't have that many resources yet but for all the extensions I used so far there is a concise guide documenting their use cases and common pitfalls. Because Quarkus is relatively new, some third party resources can be found but nowhere near the amount Spring has. For example, the stackoverflow tag has some enthusiasts and Red Hat engineers responding to questions but even though there are less questions than Spring gets, the answer rate is lower.
When issues arose which my google-fu couldn't resolve, I ended up having to search through the issue tracker. For example, in native mode I couldn't figure out why Liquibase's
includeAll isn't working or why Jackson's
@JsonValue on an
enum doesn't work without a parameter on additional annotation configuration. Maybe anecdotal evidence but in the last few years of using Spring I cannot remember having to search through their issue tracker to resolve an issue.
Dev mode works great
A Quarkus application running locally in dev mode has all kinds of nifty features by default with zero configuration. Things I really enjoyed using were live reload but also dev services (zero config testcontainers) and the dev-ui which depending on extensions installed add useful features for quickly testing and debugging a locally running application.
Another useful tool is continuous testing which works surprisingly well - even if you don't do TDD. It applies live reload to the test cycle in dev mode. Usually a feature cycle has many build- and start-application steps in between. With Quarkus, this is not necessary as the unit and integration tests just keep running with the latest live reloaded code whenever they or their coverage path change. On paper, this could save a lot of time in developer productivity but I have yet to experience if this methodology holds up in a large, complex project.
So it's over for Spring?
Not at all. Spring quickly learns what works well and then adapts that albeit at a much slower pace. Since Spring Boot 3, native support is no longer experimental. Spring Boot 3.1 (released a few days ago) added initial support for testcontainers at development time and a maven goal to start a "test run" which I suspect will creep closer to Quarkus' dev mode over time.
The "testcontainers at development time" aren't as sleek as Quarkus' automatic dev services: Spring still requires some configuration as to which containers to start. Quarkus has zero configuration testcontainers (dev services) for pretty much all the official extensions integrating with an external service. Spring doesn't move with the speed of innovation of Quarkus, but it's definitely not a project without development and new features being added.
There is also a sizable age difference (roughly 16 years!) between both frameworks. In terms of usage, (human) resources available and maturity of ecosystem Spring wins by a large margin. Some would call that legacy, others define it as battle hardened. In my opinion, competition is good and both can learn from each other's strengths and adapt it in a way they seem fit. Looking at the current trend, Quarkus overtaking Spring is unlikely but it will definitely continue to capture some of its market share.
For a silly weekend project like this, the difference in developer productivity is negligible and hard to measure. If I had chosen to migrate to Spring Boot, it might even be done faster because I have more muscle memory in working with it. The differences in productivity will show over time and probably not at all in a project of this size especially because I still had to get up to steam with Quarkus.
Now that this project is containerized, easily & rapidly deployable and in a familiar tech-stack, the barrier and cost (my hobby time) to make changes is lower. For me, it's a good playground to test drive Quarkus more. Would I choose Quarkus for actual mission critical applications? It depends.
Typically a "Spring project" is easier to modernize to the most recent version of Spring than rewriting it to Quarkus and will roughly get you the same container and native benefits Quarkus offers. Even for a legacy, badly architectured Spring monolith it might be more cost efficient to incrementally upgrade it than to completely rewrite it as Quarkus application(s). Quarkus does not yet have a clear LTS-policy so even for greenfield projects it really depends on the situation. For example, if there is a team in place already proficient in Spring, adopting Quarkus would make little sense.
You should definitely not use this write-up of a silly weekend project as a reference point but rather evaluate and make an informed decision depending on your own context and requirements. Either way, I enjoyed working with and learning about Quarkus and will keep an eye on future developments.
Want more content like this?
Hi! I sporadically write about tech & business. If you want an email when something new comes out, feel free to subscribe. It's free and I hate spam as much as you do.