HTMX + Thymeleaf vs SPA: What Should Backend Devs Choose?
I’ve been working with Spring Boot for years. Backend development? That’s my comfort zone. But when it comes to building interactive web applications, I’ve always felt stuck.
The traditional approach—server-side templating with Thymeleaf—feels outdated. The modern approach—React, Angular, Vue—requires diving deep into JavaScript build tools, npm, TypeScript, and an entirely different ecosystem. Neither option felt quite right.
Recently, I discovered HTMX combined with Thymeleaf. It’s been a game-changer for my workflow. Let me share what I’ve learned about this stack versus traditional SPA frameworks.
The Backend Developer’s Dilemma
Here’s the situation many of us face:
- You know Spring Boot inside and out
- Building APIs is second nature
- But building interactive UIs? That’s another story
On r/Java, I came across a discussion titled “8 years Spring, zero frontend - JTE or Angular?” The original poster captured the exact sentiment I’ve felt. They wanted to avoid the Node/JavaScript world but weren’t sure if that was the right call given market trends.
The responses were telling. One user, smutje187, suggested: “Thymeleaf plus htmx is also great - or plain JavaScript without any heavyweight framework.”
Another, nozomashikunai_keiro, confirmed: “+1 for Thymeleaf/HTMX. Very very nice to work with those. (Can add Hyperscript as well since is made by people who made HTMX).”
This validated what I’d been thinking. There is a middle ground.
HTMX + Thymeleaf: The Middle Path
HTMX is a library that lets you access AJAX, CSS transitions, WebSockets, and Server Sent Events directly in HTML. No JavaScript required. Combined with Thymeleaf, Spring Boot’s native templating engine, it creates a powerful server-side rendering approach.
The beauty of this stack? You stay in your comfort zone. Everything is Java. Everything is server-side.
Here’s a practical example: a simple user management interface.
HTMX + Thymeleaf Example
The HTML template with HTMX attributes:
<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head> <title>User Management</title> <script src="/static/htmx.min.js"></script></head><body> <h1>User List</h1>
<form hx-post="/users" hx-target="#user-table" hx-swap="innerHTML"> <input type="text" name="username" placeholder="Username"> <input type="email" name="email" placeholder="Email"> <button type="submit">Add User</button> </form>
<table id="user-table"> <thead> <tr> <th>Username</th> <th>Email</th> <th>Actions</th> </tr> </thead> <tbody> <tr th:each="user : ${users}"> <td th:text="${user.username}"></td> <td th:text="${user.email}"></td> <td> <button hx-delete="/users/{user.id}" hx-target="closest tr" hx-swap="outerHTML" hx-confirm="Delete this user?"> Delete </button> </td> </tr> </tbody> </table></body></html>And the Spring controller:
@Controller@RequestMapping("/users")public class UserController {
@GetMapping public String listUsers(Model model) { model.addAttribute("users", userService.findAll()); return "user-list"; }
@PostMapping public String createUser(@ModelAttribute User user, Model model) { userService.save(user); model.addAttribute("users", userService.findAll()); return "user-list :: #user-table"; // Fragment for partial update }
@DeleteMapping("/{id}") public String deleteUser(@PathVariable Long id) { userService.deleteById(id); return ""; // Empty response, HTMX removes the row }}That’s it. About 50 lines of HTML and 15 lines of Java controller code. Form submission works. Table updates dynamically. Delete buttons confirm before action. No JavaScript framework needed.
The SPA Framework Approach
For comparison, here’s the same functionality using React:
import { useState, useEffect } from 'react';
interface User { id: number; username: string; email: string;}
export default function UserList() { const [users, setUsers] = useState<User[]>([]); const [formData, setFormData] = useState({ username: '', email: '' });
useEffect(() => { fetchUsers(); }, []);
const fetchUsers = async () => { const response = await fetch('/api/users'); const data = await response.json(); setUsers(data); };
const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData), }); setFormData({ username: '', email: '' }); fetchUsers(); };
const handleDelete = async (id: number) => { if (!confirm('Delete this user?')) return; await fetch(`/api/users/${id}`, { method: 'DELETE' }); setUsers(users.filter(user => user.id !== id)); };
return ( <div> <h1>User Management</h1>
<form onSubmit={handleSubmit}> <input type="text" placeholder="Username" value={formData.username} onChange={(e) => setFormData({...formData, username: e.target.value})} /> <input type="email" placeholder="Email" value={formData.email} onChange={(e) => setFormData({...formData, email: e.target.value})} /> <button type="submit">Add User</button> </form>
<table> <thead> <tr> <th>Username</th> <th>Email</th> <th>Actions</th> </tr> </thead> <tbody> {users.map(user => ( <tr key={user.id}> <td>{user.username}</td> <td>{user.email}</td> <td> <button onClick={() => handleDelete(user.id)}> Delete </button> </td> </tr> ))} </tbody> </table> </div> );}And the Spring REST API:
@RestController@RequestMapping("/api/users")public class UserApiController {
@GetMapping public List<User> listUsers() { return userService.findAll(); }
@PostMapping public User createUser(@RequestBody User user) { return userService.save(user); }
@DeleteMapping("/{id}") public void deleteUser(@PathVariable Long id) { userService.deleteById(id); }}The React version is about 80 lines of TypeScript plus 10 lines of Java controller. Then there’s the build configuration—webpack or Vite, npm setup, TypeScript configuration. The complexity adds up quickly.
When to Choose HTMX + Thymeleaf
Based on my experience, HTMX + Thymeleaf excels for:
- Internal tools and dashboards: These typically need basic interactivity without complex state management
- CRUD applications with moderate interactivity: Form submissions, table updates, search and filtering
- Teams without frontend specialists: Backend developers can build the entire application
- Projects where rapid development matters: Less setup, less boilerplate, faster iteration
The stack offers several advantages:
- Team velocity: Backend developers build UIs faster
- Maintenance: Single language stack (Java) reduces cognitive load
- Performance: Server-side rendering means faster initial page loads
- SEO: Traditional rendering is search engine friendly
- Simplicity: No npm, no build tools, no JavaScript framework to learn
When to Choose SPA Frameworks
SPA frameworks (React, Angular, Vue) are the right choice when:
- Complex client-side state management: Real-time collaboration, drag-and-drop interfaces
- Rich client-side interactions: Single-page applications that don’t reload
- Targeting frontend-specific roles: SPA skills have higher market demand for frontend positions
- Building consumer-facing web apps: Where cutting-edge UX is critical
As one redditor noted: “If you want to match the market, go React/Angular. You want something easy, fast learning curve with excellent performance and production friendliness (but way less popular in the market), go Vue.js.”
Common Mistakes to Avoid
I’ve seen teams make these mistakes repeatedly:
- Choosing HTMX + Thymeleaf for highly complex applications requiring rich client-side state
- Choosing SPA frameworks for simple CRUD applications where server-side rendering is sufficient
- Underestimating the learning curve of JavaScript build tools (webpack, Vite, npm ecosystem)
- Making technology decisions based solely on market trends rather than team expertise
- Overlooking HTMX’s limitations for complex UI interactions (drag-and-drop, real-time collaboration)
The key is to align the technology choice with project requirements, team expertise, and long-term maintenance considerations.
My Recommendation
For backend developers with Spring/Java expertise looking to build interactive web applications, I recommend starting with HTMX + Thymeleaf. It’s a pragmatic path that lets you stay in the Java ecosystem while delivering dynamic, responsive user interfaces.
I’ve built several internal tools and dashboards with this stack. The development experience is smooth. The code is maintainable. I don’t miss the JavaScript ecosystem.
However, if you’re building a complex consumer-facing application with rich client-side interactions, or if you’re targeting frontend-specific career paths, investing in React/Angular/Vue remains valuable.
The best choice depends on your specific situation—not market trends, not personal comfort zones, but what actually works for your project and team.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments