This project provides annotations, helper classes and a Thymeleaf dialect to make it easy to work with htmx in a Spring Boot application.
More information about htmx can be viewed on their website.
The project provides the following libraries, which are available on Maven Central, so it is easy to add the desired dependency to your project.
Provides annotations and helper classes.
<dependency>
<groupId>io.github.wimdeblauwe</groupId>
<artifactId>htmx-spring-boot</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
Provides a Thymeleaf dialect to easily work with htmx attributes.
<dependency>
<groupId>io.github.wimdeblauwe</groupId>
<artifactId>htmx-spring-boot-thymeleaf</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
The included Spring Boot Auto-configuration will enable the htmx integrations.
See Request Headers Reference for the related htmx documentation.
Methods can be annotated with @HxRequest
to be selected when an htmx-based request (ie hx-get
) is made.
@GetMapping("/users")
@HxRequest // Called when hx-get request to '/users/' is made
public String htmxRequest(HtmxRequest details){
service.doSomething(details);
return "partial";
}
@GetMapping("/users") // Only called on a full page refresh, not an htmx request
public String normalRequest(HtmxRequest details){
service.doSomething(details);
return "users";
}
These annotations allow for composition if you wish to combine them,
so you could combine annotations to make a custom @HxGetMapping
.
The HtmxRequest
object can be injected into controller methods to check the various htmx request headers.
@GetMapping
@ResponseBody
public String htmxRequestDetails(HtmxRequest htmxReq) { // HtmxRequest is injected
if(htmxReq.isHistoryRestoreRequest()){
// ...
}
return "";
}
See Response Headers Reference for the related htmx documentation.
Setting the hx-trigger
header triggers an event when the response is swapped in by htmx.
The @HxTrigger
annotation supports doing that for you:
@GetMapping("/users")
@HxRequest
@HxTrigger("userUpdated") // 'userUpdated' event will be triggered by htmx
public String hxUpdateUser(){
return "users";
}
htmx supports updating multiple targets by returning multiple partials in a single response with
hx-swap-oob
. Return partials using this library use the HtmxResponse
as a return
type:
@GetMapping("/partials/main-and-partial")
public HtmxResponse getMainAndPartial(Model model){
model.addAttribute("userCount", 5);
return new HtmxResponse()
.addTemplate("users :: list")
.addTemplate("users :: count");
}
An HtmxResponse
can be formed from view names, as above, or fully resolved View
instances, if the controller knows how
to do that, or from ModelAndView
instances (resolved or unresolved). For example:
@GetMapping("/partials/main-and-partial")
public HtmxResponse getMainAndPartial(Model model){
return new HtmxResponse()
.addTemplate(new ModelAndView("users :: list")
.addTemplate(new ModelAndView("users :: count", Map.of("userCount",5));
}
Using ModelAndView
means that each fragment can have its own model (which is merged with the controller model before rendering).
The library has an HxRefreshHeaderAuthenticationEntryPoint
that you can use to have htmx force a full page browser
refresh in case there is an authentication failure.
If you don't use this, then your login page might be appearing in place of a swap you want to do somewhere.
See htmx-authentication-error-handling
blog post for detailed information.
To use it, add it to your security configuration like this:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
// probably some other configurations here
var entryPoint = new HxRefreshHeaderAuthenticationEntryPoint();
var requestMatcher = new RequestHeaderRequestMatcher("HX-Request");
http.exceptionHandling(exception ->
exception.defaultAuthenticationEntryPointFor(entryPoint, requestMatcher));
return http.build();
}
See Attribute Reference for the related htmx documentation.
The Thymeleaf dialect has appropriate processors that enable Thymeleaf to perform calculations and expressions in htmx-related attributes.
Note The
:
colon instead of the typical hyphen.
hx:get
: This is a Thymeleaf processing enabled attributehx-get
: This is just a static attribute if you don't need the Thymeleaf processing
For example, this Thymeleaf template:
<div hx:get="@{/users/{id}(id=${userId})}" hx-target="#otherElement">Load user details</div>
Will be rendered as:
<div hx-get="/users/123" hx-target="#otherElement">Load user details</div>
The Thymeleaf dialect has corresponding processors for most of the hx-*
attributes.
Please open an issue if something is missing.
Note Be careful about using
#
in the value. If you dohx:target="#mydiv"
, then this will not work as Thymeleaf uses the#
symbol for translation keys. Either usehx-target="#mydiv"
orhx:target="${'#mydiv'}"
The hx-vals attribute allows to add to the parameters that will be submitted with the AJAX request. The value of the attribute should be a JSON string.
The library makes it a bit easier to write such a JSON string by adding support for inline maps.
For example, this Thymeleaf expression:
<div hx:vals="${ {id: user.id } }"></div>
will render as:
<div hx-vals="{&quot;id&quot;: 1234 }"></div>
(Given user.id
has the value 1234
)
Links to articles and blog posts about this library:
- Release 1.0.0 and 2.0.0 of htmx-spring-boot-thymeleaf
- Htmx authentication error handling
- Thymeleaf and htmx with out of band swaps
Library version | Spring Boot | Minimum Java version |
---|---|---|
3.0.0 | 3.1.x | 17 |
2.2.0 | 3.0.x | 17 |
1.0.0 | 2.7.x | 11 |
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.
To release a new version of the project, follow these steps:
- Update
pom.xml
with the new version (Usemvn versions:set -DgenerateBackupPoms=false -DnewVersion=<VERSION>
) - Commit the changes locally.
- Tag the commit with the version (e.g.
1.0.0
) and push the tag. - Create a new release in GitHub via https://github.com/wimdeblauwe/htmx-spring-boot/releases/new
- Select the newly pushed tag
- Update the release notes. This should automatically start the release action.
- Update
pom.xml
again with the nextSNAPSHOT
version. - Close the milestone in the GitHub issue tracker.