DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Configurable Feign Client Retry With Reusable Library and DRY
  • How to Build a New API Quickly Using Spring Boot and Maven
  • Using Lombok Library With JDK 23
  • Scrolling With Konva.js and React

Trending

  • Scalability 101: How to Build, Measure, and Improve It
  • Stateless vs Stateful Stream Processing With Kafka Streams and Apache Flink
  • Immutable Secrets Management: A Zero-Trust Approach to Sensitive Data in Containers
  • Scalable System Design: Core Concepts for Building Reliable Software
  1. DZone
  2. Coding
  3. Java
  4. Server-Side Rendering With Spring Boot

Server-Side Rendering With Spring Boot

Explore WebJars, which manages client-side dependencies in your Maven POM, and Thymeleaf, a templating mechanism that integrates well with Spring Boot.

By 
Nicolas Fränkel user avatar
Nicolas Fränkel
DZone Core CORE ·
Sep. 19, 24 · Analysis
Likes (8)
Comment
Save
Tweet
Share
11.3K Views

Join the DZone community and get the full member experience.

Join For Free

Understanding the shared steps in the project setup is crucial before delving into the specifics of each client-augmenting technology. My requirements from the last post were quite straightforward:

  • I'll assume the viewpoint of a backend developer.
  • No front-end build step: no TypeScript, no minification, etc.
  • All dependencies are managed from the backend app, i.e., Maven

It's important to note that the technology I'll be detailing, except Vaadin, follows a similar approach. Vaadin, with its unique paradigm, really stands out among the approaches.

WebJars

WebJars is a technology designed in 2012 by James Ward to handle these exact requirements.

WebJars are client-side web libraries (e.g., jQuery and Bootstrap) packaged into JAR (Java Archive) files.

  • Explicitly and easily manage the client-side dependencies in JVM-based web applications
  • Use JVM-based build tools (e.g. Maven, Gradle, sbt, etc.) to download your client-side dependencies
  • Know which client-side dependencies you are using
  • Transitive dependencies are automatically resolved and optionally loaded via RequireJS
  • Deployed on Maven Central
  • Public CDN, generously provided by JSDelivr

- WebJars website

A WebJar is a regular JAR containing web assets. Adding a WebJar to a project's dependencies is nothing specific:

XML
 
<dependencies>
    <dependency>
        <groupId>org.webjars.npm</groupId>
        <artifactId>alpinejs</artifactId>
        <version>3.14.1</version>
    </dependency>
</dependencies>


The framework's responsibility is to expose the assets under a URL. For example, Spring Boot does it in the WebMvcAutoConfiguration class:

Java
 
public void addResourceHandlers(ResourceHandlerRegistry registry) {
  if (!this.resourceProperties.isAddMappings()) {
    logger.debug("Default resource handling disabled");
    return;
  }
  addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(),                  //1
    "classpath:/META-INF/resources/webjars/");
  addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
    registration.addResourceLocations(this.resourceProperties.getStaticLocations());
    if (this.servletContext != null) {
      ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
      registration.addResourceLocations(resource);
    }
  });
}


  1. The default is "/webjars/**"

Inside the JAR, you can reach assets by their respective path and name. The agreed-upon structure is to store the assets inside resources/webjars//. Here's the structure of the alpinejs-3.14.1.jar:

Plain Text
 
META-INF
  |_ MANIFEST.MF
  |_ maven.org.webjars.npm.alpinejs
  |_ resources.webjars.alpinejs.3.14.1
    |_ builds
    |_ dist
      |_ cdn.js
      |_ cdn.min.js
    |_ src
    |_ package.json


Within Spring Boot, you can access the non-minified version with /webjars/alpinejs/3.14.1/dist/cdn.js.

Developers release client-side libraries quite often. When you change a dependency version in the POM, you must change the front-end path, possibly in multiple locations. It's boring, has no added value, and you risk missing a change.

The WebJars Locator project aims to avoid all these issues by providing a path with no version, i.e., /webjars/alpinejs/dist/cdn.js. You can achieve this by adding the webjars-locator JAR to your dependencies:

XML
 
<dependencies>
    <dependency>
        <groupId>org.webjars.npm</groupId>
        <artifactId>alpinejs</artifactId>
        <version>3.14.1</version>
    </dependency>
    <dependency>
        <groupId>org.webjars</groupId>
        <artifactId>webjars-locator</artifactId>
        <version>0.52</version>
    </dependency>
</dependencies>


I'll use this approach for every front-end technology. I'll also add the Bootstrap CSS library to provide a better-looking user interface.

Thymeleaf

Thymeleaf is a server-side rendering technology.

Thymeleaf is a modern server-side Java template engine for both web and standalone environments.

Thymeleaf's main goal is to bring elegant natural templates to your development workflow — HTML that can be correctly displayed in browsers and also work as static prototypes, allowing for stronger collaboration in development teams.

With modules for Spring Framework, a host of integrations with your favourite tools, and the ability to plug in your own functionality, Thymeleaf is ideal for modern-day HTML5 JVM web development — although there is much more it can do.

- Thymeleaf

I was still a consultant when I first learned about Thymeleaf. At the time, JSP was at the end of their life. JSF were trying to replace them; IMHO, they failed.

I thought Thymeleaf was a fantastic approach: it allows you to see the results in a static environment at design time and in a server environment at development time. Even better, you can seamlessly move between one and the other using the same file. I've never seen this capability used.

However, Spring Boot fully supports Thymeleaf. The icing on the cake: the latter is available via an HTML namespace on the page. If you didn't buy into JSF (spoiler: I didn't), Thymeleaf is today's go-to SSR templating language.

Here's the demo sample from the website:

HTML
 
<table>
  <thead>
    <tr>
      <th th:text="#{msgs.headers.name}">Name</th>
      <th th:text="#{msgs.headers.price}">Price</th>
    </tr>
  </thead>
  <tbody>
    <tr th:each="prod: ${allProducts}">
      <td th:text="${prod.name}">Oranges</td>
      <td th:text="${#numbers.formatDecimal(prod.price, 1, 2)}">0.99</td>
    </tr>
  </tbody>
</table>


Here is a Thymeleaf 101, in case you need to familiarise yourself with the technology.

  • When you open the HTML file, the browser displays the regular value inside the tags, i.e., Name and Price. When you use it in the server, Thymeleaf kicks in and renders the value computed from th:text, #{msgs.headers.name} and #{msgs.headers.price}.
  • The $ operator queries for a Spring bean of the same name passed to the model. ${prod.name} is equivalent to model.getBean("prod").getName()".
  • The # calls a function.
  • th:each allows for loops.

Thymeleaf Integration With the Front-End Framework

Most, if not all, front-end frameworks work with a client-side model. We need to bridge between the server-side model and the client-side one.

The server-side code I'm using is the following:

Kotlin
 
data class Todo(val id: Int, var label: String, var completed: Boolean = false) //1

fun config() = beans {
  bean {
    mutableListOf(                                                              //2
      Todo(1, "Go to the groceries", false),
      Todo(2, "Walk the dog", false),
      Todo(3, "Take out the trash", false)
    )
  }
  bean {
    router {
      GET("/") {
        ok().render(                                                           //3
          "index",                                                             //4
          mapOf("title" to "My Title", "todos" to ref<List<Todo>>())           //5
        )
      }
    }
  }
}


  1. Define the Todo class.
  2. Add an in-memory list to the bean factory. In a regular app, you'd use a Repository to read from the database
  3. Render an HTML template.
  4. The template is src/main/resources/templates/index.html with Thymeleaf attributes.
  5. Put the model in the page's context.

Thymeleaf offers a th:inline="javascript" attribute on the `` tag. It renders the server-side data as JavaScript variables. The documentation explains it much better than I ever could:

The first thing we can do with script inlining is writing the value of expressions into our scripts, like:

/*    ...

   var username = /*[[${session.user.name}]]*/ 'Sebastian';

   ...
/*]]>*/


The /*[[...]]*/ syntax, instructs Thymeleaf to evaluate the contained expression. But there are more implications here:

  • Being a javascript comment (/*...*/), our expression will be ignored when displaying the page statically in a browser.
  • The code after the inline expression ('Sebastian') will be executed when displaying the page statically.
  • Thymeleaf will execute the expression and insert the result, but it will also remove all the code in the line after the inline expression itself (the part that is executed when displayed statically).

- Thymeleaf documentation

If we apply the above to our code, we can get the model attributes passed by Spring as:

HTML
 
<script th:inline="javascript">
/*<![CDATA[*/
  window.title = /*[[${title}]]*/ 'A Title'
  window.todos = /*[[${todos}]]*/ [{ 'id': 1, 'label': 'Take out the trash', 'completed': false }]
/*]]>*/
</script>


When rendered server-side, the result is:

HTML
 
<script>
/*<![CDATA[*/
  window.title = "My title";
  window.todos: [{"id":1,"label":"Go to the groceries","completed":false},{"id":2,"label":"Walk the dog","completed":false},{"id":3,"label":"Take out the trash","completed":false}]
/*]]>*/
</script>


Summary

In this post, I've described two components I'll be using throughout the rest of this series:

  • WebJars manage client-side dependencies in your Maven POM.
  • Thymeleaf is a templating mechanism that integrates well with Spring Boot.

The complete source code for this post can be found on GitHub.

Go Further

  • WebJars Instructions for Spring Boot
Apache Maven HTML Thymeleaf Spring Boot Library

Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Configurable Feign Client Retry With Reusable Library and DRY
  • How to Build a New API Quickly Using Spring Boot and Maven
  • Using Lombok Library With JDK 23
  • Scrolling With Konva.js and React

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!