IntelliJ IDEA Java

Introduction to Htmx for Spring Boot Developers

Starting a new project can be both exciting and challenging at the same time. Choosing what technologies will help you deliver your solution comes with the cost associated with those choices. Each additional technology can bring issues and dependencies that can lead to incremental decreases in progress that can grind your project to a stop. The front end is a common place where developers might experience decision fatigue. The popularity of frontend frameworks such as React, Angular, and Vue brings a lot of value and expensive tradeoffs in tooling, security considerations, network traffic, and heavy initial payloads. If you’re feeling overwhelmed when making a front-end decision for your next project, this post is for you.

In this post, we’ll explore an up-and-coming library called Htmx that allows you to leverage your existing Spring Boot knowledge to deliver interactive user experiences while circumventing some of the frustrations you may have felt with other front-end frameworks. By the end of this post, you should be confident enough to add Htmx to a new or existing Spring Boot project.

What is Htmx?

To understand what Htmx is, we must first understand philosophically what the library is attempting to accomplish. 

Htmx is unabashedly proud to be in the Hypermedia camp. Hypermedia is a term coined in 1965 by Ted Nelson, and it focuses on the idea that a single document may contain several interactive elements, such as text, images, videos, and links to other documents. If this sounds a lot like HTML, then you’d be right. HTML is the canonical example of this concept and has successfully delivered the internet we know today. Then, in 2015, the web development landscape changed.

During the Web 2.0 era, web development began moving to separate the UI elements of web experiences into frontend and backend. Backend APIs delivered JSON or XML because these payloads were smaller than full HTML payloads. The front end was responsible for transforming data into presentational HTML using JavaScript. This pattern worked around limitations in the client and rendering speed and, at the time, delivered a better user experience to visitors. Since that time, a few things have changed:

  • Internet speeds are significantly faster overall
  • Clients are much more efficient in rendering HTML
  • JavaScript and JSON payloads have ballooned
  • JavaScript tooling has become increasingly complex

Regarding Htmx, in the context of modern applications, frontend frameworks may be a more considerable burden than the problems they claim to help solve. So, how does Htmx work differently?

Htmx focuses on a declarative programming style, allowing you to decorate your existing HTML output with Htmx-specific attributes. These attributes give those HTML elements more functionality than they may typically have. The fundamental flow of all Htmx includes the following:

  • A client-side trigger that raises an event
  • The event typically triggers a web request to a server backend
  • The server responds with an HTML fragment
  • Htmx replaces an existing DOM element with the response

Let’s look at a simple Htmx example, which we will implement using Spring Boot later.

<button hx-post="/clicked"
    hx-trigger="click"
    hx-target="#parent-div"
    hx-swap="outerHTML"
>
    Click Me!
</button>

The hx- attributes allow this button to trigger an HTTP POST whenever clicked. Once the server responds, we will find the #parent-div and swap it with the resulting HTML.

These attributes are not exclusive to any HTML element and can be combined to create rich experiences. For example, here is a search box that triggers a request whenever the user changes the value:

<input type="text" name="q"
    hx-get="/trigger_delay"
    hx-trigger="keyup changed delay:500ms"
    hx-target="#search-results"
    placeholder="Search..."
>
<div id="search-results"></div>

This particular example also defines a delay of 500ms before making a request to the server, as a way to not make requests while the user is still typing, so that the server only receives the most relevant search queries and not all of them – a technique called “debouncing”.

Now that you have a general idea of Htmx, let’s add it to a Spring Boot sample project and implement the backend for both snippets.

Your first Htmx experience in Spring Boot

Before starting, I recommend installing the Htmx Support plugin for JetBrains IDEs. It will make your Htmx development experience that much better. Thanks, Hugo Mesquita!

Use the New Project dialog in IntelliJ IDEA to create a new Spring Boot project. If you already have a Spring Boot project, skip this step.

On the next screen, choose the Spring Web and Thymeleaf dependencies.

Let’s start by first creating a new HomeController class. This will be where we add our application logic for our first sample.

package org.example.htmxdemo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
    @GetMapping("/")
    public String home() {
        return "index";
    }
}

Next, let’s create our index HTML template file under resources/templates/index.html. Be sure to paste the following contents. The dependencies are already included in the head tag of the provided HTML and will be retrieved by the client when the page is rendered to users.

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <title>Getting Started: Serving Web Content</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="color-scheme" content="light dark" />
    <title>Htmx Demo</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" >
    <script src="https://unpkg.com/htmx.org@2.0.1"></script>
</head>
<body>
    <main class="container">
        <section>
        <h1>Htmx Demo</h1>
        <div id="parent-div"></div>
        <button hx-post="/clicked"
                hx-trigger="click"
                hx-target="#parent-div"
                hx-swap="outerHTML">
            Click Me!
        </button>
        </section>
    </main>
</body>
</html>

Htmx is a no-build library, meaning you don’t need any additional dependencies to use it. As you notice, in the head element of our template, we only need a script reference to the library in our HTML. I’ve also included a CSS library, PicoCSS, to make things more aesthetically pleasing. Your output may look slightly different based on your development environment’s light/dark mode settings.

Eventually, you’ll want to download and store all third-party files alongside your code for a production setting.

Next, return to the HomeController and implement the /clicked endpoint. Remember, this needs to be handled using the POST HTTP method. Using the appropriate HTTP methods to handle interactions can be essential to Htmx development. Generally, use GET for immutable calls and POST, PUT, and DELETE for mutating calls.

package org.example.htmxdemo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.time.LocalDateTime;

@Controller
public class HomeController {
    @GetMapping("/")
    public String home() {
        return "index";
    }

    @PostMapping("/clicked")
    public String clicked(Model model) {
        model.addAttribute("now", LocalDateTime.now().toString());
        return "clicked :: result";
    }
}

Finally, let’s implement our HTML fragment in clicked.html, which we’ll place next to our other template files in resources/templates/.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
  <title>fragments</title>
</head>
<body>
  <div th:fragment="result" id="parent-div">
    <p th:text="${now}"></p>
  </div>
</body>
</html>

Running our application, we can now click the button on our page and see the interface update in real-time.

Congratulations. You have successfully handled the first of many Htmx requests to come!

Now, let’s implement that search textbox for a more complex scenario.

Htmx-powered Search in Spring Boot

We will add a new search feature to our sample, implementing the snippet previously shown. Let’s first update our HTML snippet to include the search user interface. In index.html, update the contents to match the following:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <title>Getting Started: Serving Web Content</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="color-scheme" content="light dark" />
    <title>Htmx Demo</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" >
    <script src="https://unpkg.com/htmx.org@2.0.1"></script>
</head>
<body>
    <main class="container">
        <section>
        <h1>Htmx Demo</h1>
        <div id="parent-div"></div>
        <button hx-post="/clicked"
                hx-trigger="click"
                hx-target="#parent-div"
                hx-swap="outerHTML">
            Click Me!
        </button>
        </section>

        <section>
            <input type="text"
                   name="q"
                   hx-get="/search"
                   hx-trigger="keyup changed delay:500ms"
                   hx-target="#search-results"
                   placeholder="Search..."
            >
            <div th:replace="search::results">
            </div>
        </section>

    </main>
</body>
</html>

Create a new search.html file in resources/templates/ and copy the following content into the newly created file.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <title>fragments</title>
</head>
<body>
<div id="search-results" th:fragment="results">
    <ul th:each="result: ${results}">
        <li th:text="${result}"></li>
    </ul>
</div>
</body>
</html>

This file contains our response fragment, which will display the results of the user-initiated search. Let’s update our HomeController one last time:

package org.example.htmxdemo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.time.LocalDateTime;
import java.util.List;

@Controller
public class HomeController {

    static List<String> searchResults =
            List.of("one", "two", "three", "four", "five");

    @GetMapping("/")
    public String home(Model model) {
        model.addAttribute("results", searchResults);
        return "index";
    }

    @GetMapping("/search")
    public String search(String q, Model model) {
        var filtered = searchResults
                .stream()
                .filter(s -> s.startsWith(q.toLowerCase()))
                .toList();

        model.addAttribute("results", filtered);
        return "search :: results";
    }

    @PostMapping("/clicked")
    public String clicked(Model model) {
        model.addAttribute("now", LocalDateTime.now().toString());
        return "clicked :: result";
    }
}

Let’s pause here and consider what we’re doing with our HomeController class.

  1. Our home method sets the search results for the initial experience.
  2. Our index.html uses the search :: results fragment
  3. When the user types, the /search endpoint handles the query and returns the modified search :: results fragment with our new results.

All of this logic happens using our knowledge of Spring Boot and Thymeleaf. That’s pretty amazing. Rerun the application and start typing into the search box to see the filtered results.

It might not seem like it, but you implemented some complex Htmx scenarios. There’s still more to learn, and the basics described in this post are a great starting point.

Community References

Htmx has a growing community of excited developers, and we wanted to share some of their work with you. Knowing there is community enthusiasm for Htmx may help reduce your anxiety about adopting the technology.

Those willing to look past the backend technology can also check out my guide series Htmx for ASP.NET Core developers, which has many samples and techniques that can be adapted to Spring Boot.

For folks seriously getting into Htmx development, check out the Spring Boot and Thymeleaf library for htmx, which adds helpful elements to your Spring Boot application.

Also, to get the sample code featured in this post, fellow Developer Advocate Marit van Djik pushed a complete sample to her GitHub repository

Conclusion

If you suffer from front-end fatigue and prefer to work with backend tools like Spring Boot and Thymeleaf, you may consider Htmx for your next solution. In my opinion, one of Htmx’s strongest selling points is that you can layer it in incrementally. Some pages in your web application may utilize Htmx heavily, while others do not mention It. This can speed up your deliverables considerably, as you spend less time wrestling with JavaScript build tools and more time building solutions that users will love.

We hope you enjoyed this post. We’d love to hear about your experience integrating Htmx and Spring Boot. If you have any questions or feedback, please feel free to comment.

image description