Java Full Stack Development in 2026

Web development continues to move fast, especially on the front-end, and 2025 was no exception. Faster bundlers, new frameworks, and the undoubtedly march towards TypeScript and React homogeneity has brought us reusable components, better experiences (with partial UI changes) and long build times with split (front-end and back-end) codebases glued together by copious amounts of GraphQL – and that’s fine, if you have the specialized teams to handle it. 

But what if you don’t? Instead you have a small team of brilliant generalists looking for the biggest productivity boost possible? Well,  we ported www.scanii.com from a conventional webpack+typescript spring boot application to the stack outlined below with these nice gains:

  • Consolidated codebase,  with a simple “Run” target in IntelliJ
  • Frontend build times down to 0 with automatic reloading
  • Partial page renderings (like a SPA)  and you can still use React if you choose to
  • 100 points on lighthouse performance score
  • Easier debugging without source maps or a transpilation

Here’s how we did it: 

Moving all rendering Server Side 

1. Pick a template engine

Start by picking a template rendering engine supported by Spring Boot. We went with JTE mostly for personal preference, it feels more productive since the entire JDK is available in the template and the view model maps nicely to sending Page objects down – I could write a whole blog post about this alone. In any case, any template engine of your choice should work

2. Ditch the bundler

For this, you need to move to using importmaps although it might seem exotic, it is the same direction that Rails is pursuing and part of the HTML standard. In essence, it allows you to map JavaScript  module import paths to other JavaScript files. Here’s ours:

import maps usage in our codebase

Something else that turned out to be super cool is that we could tie in importmaps with webjars and have those  dependencies served directly from our spring boot app without needing a third party CDN.  The other interesting thing in that screenshot is our main entry point “/js/index.js” and as you would expect that’s where we link all of our javascript so the browser can load them: 

index file loading all other javascript files

3. Deliver interactivity

This is the idea that for certain actions the browser full page reload cycle is just too long and makes your application feel sluggish. It is the reason SPA’s were originally created for, shipping most of the app to the client at once and perform smaller partial server updates. To achieve SPA levels of interactivity with a single codebase we again followed Rails lead and decided for Turbo and Stimulus from Hotwire.dev. This gave us automatic partial page updates (with Turbo) and a familiar MVC framework (with Stimulus) for the Javascript we still needed. With that said, with importmaps in place, we could have stuck with React for component rendering and that is something we could still introduce in the future for certain components that it just doesn’t make sense to port to Stimulus.

Pain Points 

Like everything else in tech, it’s a game of tradeoffs. Here are some of the pain points we ran into: 

Missing HOT Reloading

One nice thing about modern front-end stacks is the ability to see your changes immediately (well after the bundler does its thing) reflected in the UX. We were able to close this gap with some minimal logic client side and server-side. On the client side we poll for server side changes (and on the server we monitor the filesystem for changes. This turned out to be much faster than the front-end solution we had before.

Client side: 

and the server side:

CSRF Protection

The moment you start porting large chunks of your codebase to server side rendering you will inevitable grow to hate current state of CSRF protection. Spring Security built-in tooling for protecting against those attacks relies on random tokens being included as a parameter in every form. Albeit there are ways to automate the process of adding a new input field to every form, it can be tedious and, more importantly, run into problems with Turbo page caching logic. To work around that pain point, we built a new CSRF filter based upon some nascent tech that relies on the browser on security header Sec-Fetch-Site – no more tokens sprinkled into templates or issues with partial pages reloads!

Here’s the core of our implementation for reference:

I plan on gauging the appetite of the Spring Security folks to have CSRF protection via Sec-Fetch-Site added in the future. 

That’s all folks

All in all, I’m quite happy with the end result with both the local dev loop and the production performance improved by orders of magnitude. Now, if you like to see this all live, just hop over to www.scanii.com and view-source. It’s all there, no bundles, no transpilation, no source maps and, more importantly, no magic – ok, fine, very little magic.

5 Wishes for Java in 2025

A new year means it is time to write up a totally unsolicited list of my wish lists for the Java programming language in 2025. But first, a little bit of background on me.

Java, the programming language, has been quite good to me, and for that, I’m sincerely grateful. Over the last 20 years or so, it has enabled me to build multiple successful products with relative ease – I never felt that “running Java” ever got in the way.

As some other folks have said, Java is a solid B- programming language (I would have called it B+ but hey), a general-purpose programming language that has benefited from its backwards compatibility, slow progress, and exceptional runtime.

Now, back to those wishes. I’m ranking them by “feasibility” based on my educated guess of how much work it would take to implement them. Here it goes.

1 Make Semicolon Usage Optional

This might appear purely aesthetic at first, but I would encourage you to try a language with optional semicolon-terminated statements, like Golang or Kotlin, and you will see that 99% of the time you typed ; you were following it up with a newline. In 2025, we should be able to have compilers automatically add that statement delineation for us, saving one keystroke per line of code. As a big plus, this change could be made in a completely backwards-compatible way since existing semicolon-delimited code would continue to work just fine.

Here’s an example:

Imagine the image below without the _ errors:

Now, put yourself in the shoes of your earlier self, first learning about programming languages and not fully grasping the differences between a statement and an expression. This might seem silly, but I firmly believe it is in line with the JDK developers’ goals of improving language onboarding.

2 An Official Code Formatter

Java would benefit from an official formatter. Yes, there are plenty of good formatters out there but that will never be as effective as an official one that the JDK developers themselves use to format the JDK codebase. This would save countless hours of discussions around what formatter an organization should embrace and, more importantly, as new syntax is introduced, how it should be formatted. 

3 Make com.sun.net.httpserver Servlet Compliant

Note that I didn’t say “faster” or more spec-compliant like “HTTP/2”; all we need is a decent and simple HTTP server in the JDK to enable a slew of faster prototypes. Nowadays, most web servers will be behind a load balancer anyway, so a lot of the code spent on hardening servers like Jetty and Tomcat to be bulletproof isn’t as needed.

Having a HttpServer implement the Servlet API would also open the door for any servlet-capable framework, like Spring, to support it. Based on my experience, this would be sufficient for many use cases where you’re experimenting with ideas and optimizing for “time to production” versus reliability.

4 A Built-in JSON Encoder

First of all, I’m a huge fan of Jackson, and while writing this post, I realized that I never made a sponsorship donation to cowtowncoder, so I had to stop writing for a bit to shamefully address that. But now I’m back, and let’s talk about JSON.

So, yes, Jackson is likely the gold standard in serialization libraries, and I have no complaints about it, but it would be much easier to get quickly up and running with Java if you could rely on the JDK to convert objects to and from JSON. This would not have to be in any way as good as Jackson’s in terms of performance but just “not comically slow,” since a product could eventually upgrade to Jackson later in its life.

The use case here is really helping someone with a text editor easily create an HTTP service that accepts and returns JSON, complementing item 3 above by making the built-in server slightly more capable.

Luckily, it seems that work is happening in this area as part of a broader Java serialization redesign, as covered during the last DEVOX.

5 A Built-in Build Tool

Java has some amazing IDEs and, in general, the best “assisted” development experience of any programming language. But without the help of an IDE, developing in Java can be painful and, worse, very hard to teach.

For example, compiling the basic hello world above would look like this (I’ve removed some of the –enable-preview calls for brevity):

% javac src/main/playground/Main.java
% java -cp src/main/playground Main

Now compare that to similar protocols from other languages:

Golang

% go build hello.go 
% ./hello

Dotnet

% dotnet build 
% dotnet run 

To do this right, we should also combine java and javac into a single, more user-friendly executable. I would love if we called it jdk or simply java, and maybe someday we could do something like:

% jdk build
% jdk run org.acme.Main

Oh yes, dependencies. Unfortunately, any official build tool would have to be able to do two things:

  • Be used to build the JDK itself, at least all the Java bits.
  • Handle dependency management. I would be content with something that simply defers to Maven Central for now, similar to Ivy or JBang.

So, this is definitely a big wish but hey it’s simply a wish after all. 🤞

The crazy benefits of ZGC

The chart above is for a reasonably complex application running globally across multiple AWS regions and JVMs on JDK19, the dip in average memory usage is the switch to ZGC – a whopping 50% for our busiest regions in terms of heap size.

Now, there is a lot out there about how ZGC uses memory differently and can, sometimes, surprise users but the number that matters, actual memory in use, it appears to be extremely efficient.

Well done JDK developers 👏