Java · Spring · Thymeleaf · Htmx
Production-ready Spring Boot, crafted carefully.
I'm Wim Deblauwe, a software developer specialized in Java and Spring. I write books, maintain open-source projects like error-handling-spring-boot-starter, and publish practical, battle-tested articles here.
just start typing — try thymeleaf, testcontainers or htmx
Taming Thymeleaf
Server-side rendering with Spring Boot, step by step.
Modern frontends with htmx
Hypermedia-driven UIs with Spring Boot & Thymeleaf.
Recent posts
showing 10 of 132- Sep 20, 2017 Using Spring Boot with JavaFX - Using Spring Profiles In the previous post, I showed ./blog/2017/09/18/using-spring-boot-with-javafx/[how to get started with Spring Boot and JavaFX]. Continuing with the same example, we are going to create a new implementation of the CountryService interface that talks to the Open AQ Platform API so the list of countries will be a lot bigger than our HardcodedListCountryService we have now. Using Retrofit First, we add 2 dependencies in our pom.xml to be able to use Retrofit: <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>retrofit</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>converter-jackson</artifactId> <version>2.3.0</version> </dependency> Using Retrofit, we can easily get information from the API and transform it using Jackson into Java Objects. First, we create an interface with a method for each call to the API we want to do: public interface OpenAqApi { @GET("countries") Call<OpenAqCountriesResponse> listCountries(); } Next, we create objects that correspond to the returned JSON structures: @Getter @Setter @JsonIgnoreProperties(ignoreUnknown = true) public class OpenAqCountriesResponse { private List<OpenAqCountry> results; } and @Getter @Setter @JsonIgnoreProperties(ignoreUnknown = true) public class OpenAqCountry { private String code; private String name; } Note: We use @JsonIgnoreProperties(ignoreUnknown = true) to avoid having to add properties in Java for JSON fields we are not interested in anyway. Finally, we create an OpenAqServiceImpl class that sets up Retrofit for our API: @Component public class OpenAqServiceImpl implements OpenAqService { private final OpenAqApi api; public OpenAqServiceImpl() { HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(interceptor).build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.openaq.org/v1/") .addConverterFactory(JacksonConverterFactory.create()) .client(client) .build(); api = retrofit.create(OpenAqApi.class); } @Override public Set<OpenAqCountry> listCountries() { try { OpenAqCountriesResponse response = api.listCountries().execute().body(); if (response != null) { return new HashSet<>(response.getResults()); } else { return Collections.emptySet(); } } catch (IOException e) { throw new RuntimeException(e); } } } Note: The current code also logs the HTTP requests and responses. For this to work, you need an additional dependency: <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>logging-interceptor</artifactId> <version>3.8.0</version> </dependency> If you don’t want/need this logging, you can remove the dependency. With all this plumbing in place, we can create our actual CountryServiceImpl that is quite simple: @Component public class CountryServiceImpl implements CountryService { private final OpenAqService service; public CountryServiceImpl(OpenAqService service) { this.service = service; } @Override public Set<Country> getAllCountries() { return service.listCountries().stream() .map(openAqCountry -> new Country(openAqCountry.getCode(), openAqCountry.getName())) .collect(Collectors.toSet()); } }
- Sep 18, 2017 Using Spring Boot with JavaFX In my previous post, I showed how to use JavaFX with the Raspberry Pi 3. I promised there to follow up with a post on using Spring Boot with JavaFX, so here it goes. Just as an example, I will be using the REST API service of the Open AQ Platform API as it is simple and does not require any authentication to get started. Initial Setup We will be using Maven, so we start with a minimal pom.xml that includes the latest Spring Boot version (1.5.7 at the time of writing): <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.deblauwe</groupId> <artifactId>airquality-fx</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>AirQualityFX</name> <description>JavaFX Client for the Open AQ Platform API</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> Our minimal JavaFX application will have 1 FXML file that defines the user interface and 1 Java file to get the application running. This is main.fxml: <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.Label?> <?import javafx.scene.layout.AnchorPane?> <AnchorPane xmlns="http://javafx.com/javafx"> <Label text="Main Content"/> </AnchorPane> And this is AirQualityFxApplication.java: package org.deblauwe.airqualityfx; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.geometry.Rectangle2D; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Screen; import javafx.stage.Stage; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class AirQualityFxApplication extends Application { private ConfigurableApplicationContext context; private Parent rootNode; @Override public void init() throws Exception { SpringApplicationBuilder builder = new SpringApplicationBuilder(AirQualityFxApplication.class); context = builder.run(getParameters().getRaw().toArray(new String[0])); FXMLLoader loader = new FXMLLoader(getClass().getResource("main.fxml")); loader.setControllerFactory(context::getBean); rootNode = loader.load(); } @Override public void start(Stage primaryStage) throws Exception { Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds(); double width = visualBounds.getWidth(); double height = visualBounds.getHeight(); primaryStage.setScene(new Scene(rootNode, width, height)); primaryStage.centerOnScreen(); primaryStage.show(); } @Override public void stop() throws Exception { context.close(); } } Some things to note: The class is annotated with @SpringBootApplication to enable Spring Boot We create the Spring ApplicationContext in the init() method We instruct the FXMLLoader to use the context::getBean method to load controllers. This will allow us to have Spring create the controllers using component scanning and inject them into our FXML files. Starting the application from IntelliJ IDEA shows the UI:
- Aug 26, 2017 Using JavaFX on Raspberry Pi 3 with Gluon In this post, I am going to detail the steps to get a simple JavaFX application running on the Raspberry Pi 3 using Gluon. Hardware For this setup, I used the Raspberry Pi 3 with the official 7" touchscreen connected. Raspbian Installation First off, download Raspbian from https://www.raspberrypi.org/downloads/raspbian/. I chose the "RASPBIAN STRETCH WITH DESKTOP" version and burned it on an SD-card using Etcher. Insert the SD-card in the Raspberry Pi and let it boot. The screen should look like this when done: Find out the IP address of the Raspberry Pi by running ifconfig on the Pi itself or looking at the DHCP clients list of your router. Enable SSH to be able to easily copy files to the Raspberry Pi. See the official instructions on how to do that.
- Jul 24, 2017 Enable preview of Spring REST Docs snippets in IntelliJ IDEA If you use Spring REST Docs, you will be using the Asciidoctor include directive a lot to include the request and responses that are generated from the unit tests. Drawback is that the Asciidoctor plugin for IntelliJ IDEA will not render them by default and looks like this: The reason is simple, the snippets attribute is only available when running the Maven or Gradle task to build the final documentation.
- Mar 13, 2017 Using ServletUriComponentsBuilder with Nginx Spring’s ServletUriComponentsBuilder.fromCurrentRequest() can be really convinient to build an URL from the current request. However, when using Nginx, things might work so well. The solution is simple, just add these headers to your Nginx configuration file: proxy_set_header X-Forwarded-Host $host:443; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-Port 443; proxy_set_header X-Forwarded-Proto https; UPDATE 2020-06-08 You need to enable support for this in Spring Boot by setting the following property (Since Spring Boot 2.2): server.forward-headers-strategy=native
- Jan 22, 2017 AssertJ custom assertion for ConstraintValidator tests As a follow-up to my last post on a custom validator to check if a String contains XML, I like to elaborate on how I made the unit test so readable. To repeat, here is part of the unit test again with the assertions highlighted: @Test public void givenNoXml_notValid() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); TestObject testObject = new TestObject("This is no XML string"); Set<ConstraintViolation<TestObject>> violationSet = validator.validate(testObject); assertThat(violationSet).hasViolationOnPath("xml"); } @Test public void givenXml_valid() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); TestObject testObject = new TestObject("<Node>test</Node>"); Set<ConstraintViolation<TestObject>> violationSet = validator.validate(testObject); assertThat(violationSet).hasNoViolations(); } I love using AssertJ for assertions in my unit tests, because: They are very readable They are code completion friendly You don’t have to remember if the expected value goes first or last
- Jan 21, 2017 Custom Validator to check if a String contains XML This blog post will show you how to create a custom validator to check if a String contains valid XML using the Java Validation API. Suppose you have a highly technical application that requires the user to enter some XML in a web form. You want to validate this on the client side, but also on the server side, since you should never trust your client. For the purpose of the example, suppose you have this entity:
- May 5, 2016 Angular datatables with server side pagination using Spring Data We are using angular-datatables in a project. So far, we just returned all entities from the server’s REST controller (Using Spring Boot and Spring Data on the server side). I wanted to see how I could implement server side pagination to avoid returning all records at once. I was lucky to find spring-data-jpa-datatables which makes it very easy to do. First, add the dependency in your pom.xml: <dependency> <groupId>com.github.darrachequesne</groupId> <artifactId>spring-data-jpa-datatables</artifactId> <version>2.1</version> </dependency> In my UserController, I have currently this:
- Apr 1, 2016 AssertJ custom assertion for testing ConstraintValidator implementations If you want to unit test a ConstraintValidator with AssertJ, then you can use this custom validator to make the tests more readable: import org.assertj.core.api.AbstractAssert; import javax.validation.ConstraintViolation; import java.util.Set; import java.util.stream.Collectors; public class ConstraintViolationSetAssert extends AbstractAssert<ConstraintViolationSetAssert, Set<? extends ConstraintViolation>> { public ConstraintViolationSetAssert(Set<? extends ConstraintViolation> actual) { super(actual, ConstraintViolationSetAssert.class); } public static ConstraintViolationSetAssert assertThat(Set<? extends ConstraintViolation> actual) { return new ConstraintViolationSetAssert(actual); } public ConstraintViolationSetAssert hasViolationOnPath(String path) { isNotNull(); // check condition if (!containsViolationWithPath(actual, path)) { failWithMessage("There was no violation with path <%s>. Violation paths: <%s>", path, actual.stream() .map(violation -> violation .getPropertyPath() .toString()) .collect( Collectors .toList())); } return this; } private boolean containsViolationWithPath(Set<? extends ConstraintViolation> violations, String path) { boolean result = false; for (ConstraintViolation violation : violations) { if (violation.getPropertyPath().toString().equals(path)) { result = true; break; } } return result; } } An example unit test that uses this: @Test public void givenInvalidUsername_violationConstraint() { CreateUserParameters p = new CreateUserParameters(); p.setUsername("x"); ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); Set<ConstraintViolation<CreateUserParameters>> violationSet = validator.validate(parameters); // static import for ConstraintViolationSetAssert assertThat(violationSet).hasViolationOnPath("username"); } See http://joel-costigliola.github.io/assertj/assertj-core-custom-assertions.html for the documentation if you want to create your own custom assertions using AssertJ. There is even a Maven plugin that allows to generate custom assertions automatically: http://joel-costigliola.github.io/assertj/assertj-assertions-generator-maven-plugin.html
- Sep 30, 2015 Read only EntryProcessors with Hazelcast This post is a follow-up on my post EntryProcessors and EntryBackupProcessors with Hazelcast. In the comments, Peter Veentjer from Hazelcast gave me the idea to use an EntryProcessor to read part of the data. I will show you below how to best do this. We start with a cache that has 10 User objects in it and we want to retrieve the user names of all users in the cache. Without an entry processor We can get the list of names without using an entry processor. For example: public class ReadOnlyEntryProcessorTest0 { public static void main(String[] args) throws InterruptedException { Config config = new Config(); config.setProperty("hazelcast.initial.min.cluster.size", "2"); HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config); // Take a lock so only 1 of the 2 nodes will execute the entry processor ILock doItLock = hazelcastInstance.getLock("doItLock"); doItLock.lock(); IMap<Long, User> testMap; try { testMap = hazelcastInstance.getMap("testMap"); if (!testMap.containsKey(1L)) { IntStream.rangeClosed(1, 10) .mapToObj(ReadOnlyEntryProcessorTest0::createUser) .forEach(user -> testMap.put(user.getId(), user)); testMap.values().stream().map(User::getName).forEach(name -> System.out.println("name = " + name)); } } finally { doItLock.unlock(); } } private static User createUser(int i) { User user = new User(); user.setId(i); user.setName("Wim" + i); return user; } } So the example first adds 10 User objects in the cache and then retrieves them again, getting the "name" of each User object and printing this. This means Hazelcast has to fetch (and serialize) the full User object from the other nodes running in the cluster. Since we are only interested in a small part of the object, we can use an EntryProcessor to retrieve just that.
no matches for "" — browse the full archive →