Tackling Records in Spring Boot
Java records fit perfectly in Spring Boot applications. Let’s have several scenarios where Java records can help us increase readability and expressiveness by squeezing the homologous code.
Using Records in Controllers
Typically, a Spring Boot controller operates with simple POJO classes that carry our data back over the wire to the client. For instance, check out this simple controller endpoint returning a list of authors, including their books:
@GetMapping("/authors")
public List<Author> fetchAuthors() {
return bookstoreService.fetchAuthors();
}
Here, the Author
(and Book
) can be simple carriers of data written as POJOs. But, they can be replaced by records as well. Here it is:
public record Book(String title, String isbn) {}
public record Author(String name, String genre, List<Book> books) {}
That’s all! The Jackson library (which is the default JSON library in Spring Boot) will automatically marshal instances of type Author
/Book
into JSON. In the bundled code, you can practice the complete example via the localhost:8080/authors endpoint address.
Using Records With Templates
Thymeleaf is probably the most used templating engine in Spring Boot applications. Thymeleaf pages (HTML pages) are typically populated with data carried by POJO classes, which means that Java records should work as well.
Let’s consider the previous Author
and Book
records, and the following controller endpoint:
@GetMapping("/bookstore")
public String bookstorePage(Model model) {
model.addAttribute("authors",
bookstoreService.fetchAuthors());
return "bookstore";
}
The List<Author>
returned via fetchAuthors()
is stored in the model under a variable named authors
. This variable is used to populate bookstore.html
as follows:
...
<ul th:each="author : ${authors}">
<li th:text="${author.name} + ' (' + ${author.genre} + ')'" />
<ul th:each="book : ${author.books}">
<li th:text="${book.title}" />
</ul>
</ul>
...
Done! You can check out the application Java Coding Problems SE.
Using Records for Configuration
Let’s assume that in application.properties
we have the following two properties (they could be expressed in YAML as well):
bookstore.bestseller.author=Joana Nimar
bookstore.bestseller.book=Prague history
Spring Boot maps such properties to POJO via @ConfigurationProperties
. But, a record can be used as well. For instance, these properties can be mapped to the BestSellerConfig
record as follows:
@ConfigurationProperties(prefix = "bookstore.bestseller")
public record BestSellerConfig(String author, String book) {}
Next, in BookstoreService
(a typical Spring Boot service), we can inject BestSellerConfig
and call its accessors:
@Service
public class BookstoreService {
private final BestSellerConfig bestSeller;
public BookstoreService(BestSellerConfig bestSeller) {
this.bestSeller = bestSeller;
}
public String fetchBestSeller() {
return bestSeller.author() + " | " + bestSeller.book();
}
}
In the bundled code, we have added a controller that uses this service as well.
Record and Dependency Injection
In the previous examples, we have injected the BookstoreService
service into BookstoreController
using the typical mechanism provided by SpringBoot – dependency injection via constructor (it can be done via @Autowired
as well):
@RestController
public class BookstoreController {
private final BookstoreService bookstoreService;
public BookstoreController(BookstoreService bookstoreService) {
this.bookstoreService = bookstoreService;
}
@GetMapping("/authors")
public List<Author> fetchAuthors() {
return bookstoreService.fetchAuthors();
}
}
But, we can compact this class by re-writing it as a record as follows:
@RestController
public record BookstoreController(BookstoreService bookstoreService) {
@GetMapping("/authors")
public List<Author> fetchAuthors() {
return bookstoreService.fetchAuthors();
}
}
The canonical constructor of this record will be the same as our explicit constructor. The application is available on GitHub.
Feel free to challenge yourself to find more use cases of Java records in Spring Boot applications.