At €€€-dayjob I’m in the fortunate position to be able to write a HTMX application using Quarkus and Qute’s HTML templates. As such I had the need to identify if a request was performed regularly or through a HTMX call. With a small amount of CDI code this was achieved without much fuzz.

An example of its use in a template:

{#if cdi:htmx.isHtmxRequest}
<small hx-swap-oob="outerHTML" id="lastUpdated">{lastUpdated}</small>
{/if}

The example above will include the <small> HTML fragment to be swapped out-of-bounds in the response when the request was performed using HTMX, and omit the <small> fragment when the Qute template is rendered using a normal, non-HTMX request.

HTMX will set the HX-Request: true header in the request to your server when it issues a request. Using the JAX-RS @HeatherParam("XX-Request) annotation we can retrieve this value for use in our detector.

The following CDI producer that ensures the proper HTMX headers are retrieved and made available through a request scoped Htmx object.

Design considerations:

  • Htmx can’t be a Java record, as those are final and CDI requires beans not to be such
  • Htmx must be a top level class, or a static one as CDI requires a default constructor
  • you can’t just produce a boolean or Boolean as those are not subclasseable (because CDI)
  • because Quarkus uses ARC as the CDI implementation, they advise (require?) injections to be package private
  • to retrieve CDI beans in your Qute template, the bean must be @Named
  • you can’t use @HeaderParam annotations because those are unknown to CDI
import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import jakarta.ws.rs.HeaderParam;

@Singleton
public class HtmxHeaderProducer {
    public static class Htmx {
        private boolean htmxRequest;

        private boolean htmxBoosted;

        Htmx(boolean htmxRequest, boolean htmxBoosted) {
            this.htmxRequest = htmxRequest;
            this.htmxBoosted = htmxBoosted;
        }

        public boolean isHtmxRequest() {
            return htmxRequest;
        }

        public boolean isHtmxBoosted() {
            return htmxBoosted;
        }
    }

    @Inject
    HttpHeaders headers;

    @Produces
    @Named("htmx")
    @RequestScoped
    public Htmx getHtmx() {
        var hxRequest = headers.getHeaderString("HX-Request");
        var hxBoosted = headers.getHeaderString("HX-Boosted");

        return new Htmx("true".equals(hxRequest), "true".equals(hxBoosted));
    }
}

If you want provide more HTMX-header functionality, it should be fairly easy to implement those in this CDI producer. Enjoy!