<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Thomas</title>
    <description>The latest articles on Forem by Thomas (@tleipzig).</description>
    <link>https://forem.com/tleipzig</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F453990%2F9ce9a7f8-755f-4d03-8f39-eee4e54303f4.png</url>
      <title>Forem: Thomas</title>
      <link>https://forem.com/tleipzig</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tleipzig"/>
    <language>en</language>
    <item>
      <title>Spring Modulith vs Multi-Module projects - advantages and disadvantages</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Fri, 14 Nov 2025 09:43:37 +0000</pubDate>
      <link>https://forem.com/tleipzig/spring-modulith-vs-multi-module-projects-advantages-and-disadvantages-55l</link>
      <guid>https://forem.com/tleipzig/spring-modulith-vs-multi-module-projects-advantages-and-disadvantages-55l</guid>
      <description>&lt;p&gt;&lt;strong&gt;Modularization&lt;/strong&gt; is the most important factor for the &lt;strong&gt;long-term maintainability of a growing codebase.&lt;/strong&gt; Two powerful options are available for Spring Boot projects: Spring Modulith and a multi-module setup. How do they differ, and which approach should you choose for your project?&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring Modulith and Multi-Module explained
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;With Spring Modulith,&lt;/strong&gt; the main focus is on splitting the actual Java or Kotlin code into modules. Under a main package (for example &lt;code&gt;my/company/app&lt;/code&gt;) all subpackages are treated as modules (for example &lt;code&gt;product&lt;/code&gt;, &lt;code&gt;inventory&lt;/code&gt;). Access to another module is only possible through a defined interface - usually a service in the top-level package of a module as well as other packages that are explicitly exposed. Internal details like repositories and entities are not available.&lt;/p&gt;

&lt;p&gt;The entire structure is verified by the &lt;code&gt;spring-modulith&lt;/code&gt; libraries. Other artifacts are taken from &lt;code&gt;src/main/resources&lt;/code&gt;, just like in a monolith. With this approach, practically all external libraries of the application can be used without restrictions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-module projects&lt;/strong&gt; are based on the native ability of Gradle or Maven to manage multiple subprojects and integrate them into the build process using the Spring Boot plugin. Each module has its own folder in the project (for example &lt;code&gt;app/product&lt;/code&gt;, &lt;code&gt;app/inventory&lt;/code&gt;) and each module has its own &lt;code&gt;src/main/java&lt;/code&gt; (or &lt;code&gt;src/main/kotlin&lt;/code&gt;) and &lt;code&gt;src/main/resources&lt;/code&gt; folders. These modules are built as real jars - in the final executable fat jar, all other libraries are included again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Differences in practice
&lt;/h2&gt;

&lt;p&gt;An existing monolith can be converted to a Spring Modulith project relatively quickly - you only need to add the dependency and change the package structure. IntelliJ even shows the modules and breaches directly. Hardly any changes are needed in the code itself, only the module interfaces must be defined and used correctly.&lt;/p&gt;

&lt;p&gt;For multi-module projects, no additional library is required, but the build files (&lt;code&gt;build.gradle&lt;/code&gt;, &lt;code&gt;build.gradle.kts&lt;/code&gt; or &lt;code&gt;pom.xml&lt;/code&gt;) need much more adjustment. Access is only possible along the defined dependency path - imports outside of the integrated modules are technically not possible. Restricting access to internal classes is not built in and should be defined through coding guidelines rather than code restrictions.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;integration tests that cover the entire application,&lt;/strong&gt; both approaches achieve a similar goal but in different ways.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With Spring Modulith, the annotation &lt;code&gt;@ApplicationModuleTest&lt;/code&gt; with &lt;code&gt;BootstrapMode.ALL_DEPENDENCIES&lt;/code&gt; is used. This runs a test in the current module including all dependent modules. Since Spring Modulith determines modules at runtime based on actual dependencies, missing modules may need to be added via &lt;code&gt;extraIncludes&lt;/code&gt;. This can be the case, for example, if a module only contains configuration and has no exposed services.&lt;/li&gt;
&lt;li&gt;For multi-module projects, module dependencies are always explicitly defined in the build files (for example &lt;code&gt;api project(':product')&lt;/code&gt; in a &lt;code&gt;build.gradle&lt;/code&gt;). When integration tests with &lt;code&gt;@SpringBootTest&lt;/code&gt; are run, the application automatically starts with the context available for that module. Since each module also provides its own resources, only those are actually available (for example, only the included Flyway scripts are executed).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some libraries also require additional configuration to operate in a multi-module setup. While libraries like Flyway and Thymeleaf work almost out of the box, support for others such as jte is missing completely and must be provided separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing an approach
&lt;/h2&gt;

&lt;p&gt;Both modularization approaches achieve the &lt;strong&gt;main goal of logically splitting the code and thus ensuring long-term maintainability.&lt;/strong&gt; This makes both approaches clearly better than a monolith, often called the “big ball of mud.” In both cases, it is important to design the module structure around your domain, not around technical layers - so better &lt;code&gt;product&lt;/code&gt;, &lt;code&gt;inventory&lt;/code&gt;, &lt;code&gt;order&lt;/code&gt; instead of &lt;code&gt;services&lt;/code&gt;, &lt;code&gt;controller&lt;/code&gt;, &lt;code&gt;frontend&lt;/code&gt;. More details in &lt;a href="https://bootify.io/multi-module/best-practices-for-spring-boot-multi-module.html" rel="noopener noreferrer"&gt;best practices for multi-module projects&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Spring Modulith differs less from a monolith, so the learning and configuration effort is smaller. Interfaces need to be clearly defined, which can be either an advantage or a disadvantage. A special test can also generate documentation of the current module structure.&lt;/p&gt;

&lt;p&gt;The higher setup effort of &lt;strong&gt;multi-module projects comes with the benefit of real, logical modules.&lt;/strong&gt; All artifacts are directly assigned to a module, making it easier to split work within a team. The code itself is not aware of the modules, since the structure is managed by Maven or Gradle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3-eu-central-1.amazonaws.com%2Fbootify-prod%2Fext%2Fimg%2FmultiModule%2Fmodularization-spring-boot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3-eu-central-1.amazonaws.com%2Fbootify-prod%2Fext%2Fimg%2FmultiModule%2Fmodularization-spring-boot.png" title="Overview of modularization options in Spring Boot" width="625" height="385"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;  Overview of modularization options in Spring Boot&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In both approaches, individual modules can later be separated into microservices if needed. However, this step should be carefully considered - architecture is not an end in itself, but the added benefits should clearly outweigh the new costs.&lt;/p&gt;

&lt;p&gt;In the Bootify Builder, you can &lt;strong&gt;create a modularized Spring Boot app using either Spring Modulith or the multi-module approach.&lt;/strong&gt; Define your &lt;a href="https://bootify.io/docs/modules-tab.html" rel="noopener noreferrer"&gt;custom module structure&lt;/a&gt;, including the assignments of entities and Spring Security configs, and get the full setup directly in the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bootify.io/pricing.html" rel="noopener noreferrer"&gt;&lt;strong&gt;» See Features and Pricing&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>modulith</category>
      <category>maven</category>
      <category>gradle</category>
    </item>
    <item>
      <title>Integrate jte with Spring Boot</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Wed, 27 Aug 2025 13:27:20 +0000</pubDate>
      <link>https://forem.com/tleipzig/integrate-jte-with-spring-boot-e7i</link>
      <guid>https://forem.com/tleipzig/integrate-jte-with-spring-boot-e7i</guid>
      <description>&lt;p&gt;Jte is a modern template engine for Spring Boot that &lt;strong&gt;stands out for its minimalist syntax.&lt;/strong&gt; What steps are necessary to integrate jte into our application and make it ready for use in the real world?&lt;/p&gt;

&lt;p&gt;The current sample code for the setup described &lt;a href="https://github.com/bootify-io/spring-boot-jte-example" rel="noopener noreferrer"&gt;is available here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application configuration
&lt;/h2&gt;

&lt;p&gt;The starting point is a Spring Boot application based on Maven without a frontend. We can easily create this using Bootify with &lt;a href="https://bootify.io/" rel="noopener noreferrer"&gt;start project&lt;/a&gt;. We could already choose jte as the frontend, however we would like to manually review the exact steps involved.&lt;/p&gt;

&lt;p&gt;First, we expand our &lt;code&gt;pom.xml&lt;/code&gt; with the following points.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;project&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://maven.apache.org/POM/4.0.0"&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
        &lt;span class="na"&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;gg.jte&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jte-spring-boot-starter-3&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.2.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;gg.jte&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jte&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.2.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.webjars&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;bootstrap&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;5.3.7&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;profiles&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;profile&amp;gt;&lt;/span&gt;local&lt;span class="nt"&gt;&amp;lt;/profile&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/profiles&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;gg.jte&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jte-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.2.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;sourceDirectory&amp;gt;&lt;/span&gt;${project.basedir}/src/main/jte&lt;span class="nt"&gt;&amp;lt;/sourceDirectory&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;targetDirectory&amp;gt;&lt;/span&gt;${project.build.directory}/classes&lt;span class="nt"&gt;&amp;lt;/targetDirectory&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;contentType&amp;gt;&lt;/span&gt;Html&lt;span class="nt"&gt;&amp;lt;/contentType&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;process-classes&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;precompile&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  New dependencies and plugin config for jte&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This adds &lt;strong&gt;jte, Bootstrap as a WebJar and a Spring Boot starter&lt;/strong&gt; that expects all templates to be located in &lt;code&gt;src/main/jte&lt;/code&gt;. To ensure that these are recompiled during development after every change, we add an &lt;code&gt;application-local.properties&lt;/code&gt; file with the following entries to our project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;gg.jte.usePrecompiledTemplates&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;
&lt;span class="py"&gt;gg.jte.developmentMode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During development, the &lt;code&gt;local&lt;/code&gt; profile should be active so that these settings are used. We extend the existing &lt;code&gt;application.properties&lt;/code&gt; with the following setting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;gg.jte.usePrecompiledTemplates&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Precompiled templates are expected in the classpath&lt;/strong&gt; with this setting - this is already handled by our configured plugin, which stores all artifacts in &lt;code&gt;target/classes&lt;/code&gt; so that they are integrated into our fat jar.&lt;/p&gt;

&lt;p&gt;We need a helper to &lt;strong&gt;make further required functions available in our templates.&lt;/strong&gt; To avoid having to define and pass an object as a parameter everywhere, we use a helper class with static methods.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JteContext&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;LocalizationSupport&lt;/span&gt; &lt;span class="n"&gt;localizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nl"&gt;WebUtils:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ThreadLocal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ModelMap&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ThreadLocal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ModelMap&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;JteContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;JteContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Content&lt;/span&gt; &lt;span class="nf"&gt;localize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;localizer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;localize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;ModelMap&lt;/span&gt; &lt;span class="nf"&gt;getModel&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getMsgSuccess&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MSG_SUCCESS&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getMsgInfo&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MSG_INFO&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getMsgError&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MSG_ERROR&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getRequestUri&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;WebUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequest&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getRequestURI&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Helper methods for our templates&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;LocalizationSupport&lt;/code&gt; is the recommended interface for internationalization. An interceptor is responsible for setting and removing the model for the duration of a request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JteContextInterceptor&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;HandlerInterceptor&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;postHandle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ModelAndView&lt;/span&gt; &lt;span class="n"&gt;modelAndView&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modelAndView&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;JteContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modelAndView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getModelMap&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;afterCompletion&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;JteContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reset&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to use all defined functions in our templates using &lt;code&gt;@import static io.bootify.jte.util.JteContext.*&lt;/code&gt; and extend them as needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layout and first pages
&lt;/h2&gt;

&lt;p&gt;Real &lt;strong&gt;enterprise applications usually require a number of features&lt;/strong&gt; that we want to integrate into our application. Firstly we want to add a &lt;code&gt;layout.jte&lt;/code&gt;. In abbreviated form, it looks like this - including a parameter &lt;code&gt;content&lt;/code&gt;, which we use to integrate the actual content of a template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@import static io.bootify.jte.util.JteContext.*

@param gg.jte.Content pageTitle
@param gg.jte.Content content


&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;${pageTitle} - ${localize("app.title")}&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/webjars/bootstrap/5.3.7/css/bootstrap.min.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/webjars/bootstrap/5.3.7/js/bootstrap.bundle.min.js"&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;header&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bg-light"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"navbar navbar-light navbar-expand-md"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"navbar-brand"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/images/logo.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"${localize("&lt;/span&gt;&lt;span class="na"&gt;app.title&lt;/span&gt;&lt;span class="err"&gt;")}"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"d-inline-block align-top"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ps-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${localize("app.title")}&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"my-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                @if (getMsgSuccess() != null)
                    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"alert alert-success mb-4"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${getMsgSuccess()}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
                @elseif (getMsgInfo() != null)
                    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"alert alert-info mb-4"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${getMsgInfo()}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
                @elseif (getMsgError() != null)
                    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"alert alert-danger mb-4"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${getMsgError()}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
                @endif
                ${content}
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  A simple jte layout&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This allows us to continue with the actual pages. A file &lt;code&gt;src/main/jte/error.jte&lt;/code&gt; is automatically picked up by Spring Boot for error pages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@import static io.bootify.jte.util.JteContext.*

@param Integer status
@param String error


@template.layout(pageTitle=localize("error.page.headline", status, error), content=@`
    &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${status} - ${error}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;${localize("error.page.message")}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
`)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  A simple error page&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here, we are using our prepared layout directly - the principle is clear. A homepage could look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@import static io.bootify.jte.util.JteContext.*


@template.layout(pageTitle=localize("home.index.headline"), content=@`
    &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${localize("home.index.headline")}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${localize("home.index.text")}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-md-4 mb-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h4&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${localize("home.index.exploreEntities")}&lt;span class="nt"&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"list-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/products"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"list-group-item list-group-item-action"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${localize("product.list.headline")}&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
`)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have already completed all necessary steps so that &lt;strong&gt;our application compiles and displays jte templates.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More complex setups
&lt;/h2&gt;

&lt;p&gt;The next step is to make our application &lt;strong&gt;support forms based on Spring MVC.&lt;/strong&gt; There are no direct functions from jte for this - instead, we can extend our &lt;code&gt;JteContext&lt;/code&gt; class with practical helpers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JteContext&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;BindingResult&lt;/span&gt; &lt;span class="nf"&gt;getBindingResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;BindingResult&lt;/span&gt; &lt;span class="n"&gt;bindingResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;BindingResult&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"org.springframework.validation.BindingResult."&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bindingResult&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;emptyBindingResult&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bindingResult&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getFieldValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="n"&gt;getBindingResult&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getFieldValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@SuppressWarnings&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unchecked"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;T&lt;/span&gt; &lt;span class="nf"&gt;getFieldValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt; &lt;span class="n"&gt;classField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClass&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getDeclaredField&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;classField&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAccessible&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="n"&gt;classField&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Extensions for form handling&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second variant of &lt;code&gt;getFieldValue&lt;/code&gt; is needed to obtain the value with its actual type. Jte templates are &lt;strong&gt;always compiled into real Java code,&lt;/strong&gt; so casting may be necessary. Additional functions such as &lt;code&gt;isAuthenticated()&lt;/code&gt; for Spring Security can be added to &lt;code&gt;JteContext&lt;/code&gt; as needed.&lt;/p&gt;

&lt;p&gt;Complete CRUD functionality and an &lt;code&gt;inputRow.jte&lt;/code&gt; template can be seen in the &lt;a href="https://github.com/bootify-io/spring-boot-jte-example" rel="noopener noreferrer"&gt;Spring Boot jte example&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Jte's minimalist approach is very appealing, especially if you are used to the complexity of Thymeleaf. Although you have to write your own helpers for some important features, the &lt;strong&gt;code is very easy to understand and extend.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bootify.io/quickstart.html" rel="noopener noreferrer"&gt;&lt;strong&gt;» Learn more&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>jte</category>
      <category>java</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>The JHipster alternative for modern Spring Boot apps</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Sat, 09 Aug 2025 12:37:03 +0000</pubDate>
      <link>https://forem.com/tleipzig/the-jhipster-alternative-for-modern-spring-boot-apps-1blg</link>
      <guid>https://forem.com/tleipzig/the-jhipster-alternative-for-modern-spring-boot-apps-1blg</guid>
      <description>&lt;p&gt;JHipster is a &lt;strong&gt;long established OpenSource framework to generate and run Java applications&lt;/strong&gt;. New apps are created based on a custom syntax (JDL), which also includes the database schema.&lt;/p&gt;

&lt;p&gt;Bootify also allows the generation of modern microservices and web applications. As with JHipster, &lt;strong&gt;it simplifies the creation of entities and provides CRUD functionality&lt;/strong&gt; for the frontend and REST API. Bootify is based on a freemium model and runs completely in the browser.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Thanks for making such a neat tool. It's like a cleaner, sensible JHipster!"&lt;/p&gt;

&lt;p&gt;-- &lt;cite&gt;Joachim D., Senior Software Developer&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What are the advantages of Bootify over JHipster?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Pure Spring Boot without vendor lock-in&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Application development with JHipster is done with its own library and CLI to continuously maintain an application, even after the initial creation. However, this also imposes processes that must be learned and followed. Bootify generates &lt;strong&gt;pure Spring Boot applications following best practices&lt;/strong&gt;, so a developer does not need to have any specific knowledge.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Follow your own preferences&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Numerous features are available in Bootify or Spring Boot that cannot be implemented with JHipster. For example, Bootify can be used to prepare an application with Lombok or multiple modules. The frontend can be based on Thymeleaf (SSR), Angular or React (SPA), &lt;strong&gt;following their current recommendations for the development approach and syntax.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Start with the concept&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Individual preferences, database schema, REST APIs and frontend forms can be specified in the browser before writing the first line of code. This allows to &lt;strong&gt;iteratively improve the concept as a team&lt;/strong&gt; before the runnable application is downloaded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3-eu-central-1.amazonaws.com%2Fbootify-prod%2Fext%2Fimg%2Falternatives%2Fjhipster-bootify-code-preview.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3-eu-central-1.amazonaws.com%2Fbootify-prod%2Fext%2Fimg%2Falternatives%2Fjhipster-bootify-code-preview.png" title="Review your generated code in the browser" width="800" height="384"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;  Review your generated code in the browser&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Build prototypes for the real world&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Production-focused applications are mostly not limited to pure CRUD functions, but implement actual use cases. For this purpose, &lt;strong&gt;custom REST APIs and forms can be designed&lt;/strong&gt; in Bootify's Professional plan. The generated code contains only the necessary elements and no overhead that requires long-term maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should Bootify be chosen?
&lt;/h2&gt;

&lt;p&gt;JHipster enables the creation of solid applications, especially if the technology is already used and established in the company. For new applications you have to evaluate if the provided features are exactly what you need - &lt;strong&gt;deviations from the given path in JHipster are not possible at all or only with high effort&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Using Bootify, only the exact code needed for your chosen configuration is generated, &lt;strong&gt;thus providing a lightweight alternative to JHipster&lt;/strong&gt;. The application can be reviewed online by the entire team and is &lt;strong&gt;completely under your control once downloaded&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bootify.io/pricing.html" rel="noopener noreferrer"&gt;&lt;strong&gt;» See Features and Pricing&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>scaffolding</category>
      <category>java</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Integrating Spring Boot with Angular</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Sat, 28 Jun 2025 13:19:04 +0000</pubDate>
      <link>https://forem.com/tleipzig/integrating-spring-boot-with-angular-42kc</link>
      <guid>https://forem.com/tleipzig/integrating-spring-boot-with-angular-42kc</guid>
      <description>&lt;p&gt;Angular is a powerful, mature library for single page applications. While Angular runs completely in the client, Spring Boot can be used to provide the backend with all its functions. What are the steps &lt;strong&gt;to integrate Angular in its current version 20 into our Spring Boot app?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using Bootify, a Spring Boot application with Angular as the frontend stack can be created directly in the browser - &lt;a href="https://bootify.io/" rel="noopener noreferrer"&gt;start project&lt;/a&gt; here. All the points described below are already implemented and further settings can be selected according to your preferences. In order to understand the following steps, we will &lt;strong&gt;start with a simple application from Bootify without a frontend&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In general, we try to follow the recommendations and standards of both frameworks in their current version as closely as possible - but a few adjustments are necessary so that both work together optimally and all development processes are supported.&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular setup
&lt;/h2&gt;

&lt;p&gt;A new Angular client is created with its CLI (when not using Bootify). &lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; must be available on the system first; then we add the CLI as a global dependency.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g @angular/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then create our new Anguar project - the subfolder is created automatically. We select SCSS as the language for the styles and server-side rendering is not supported.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ng new angularclient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now &lt;strong&gt;move the generated client application completely into our Spring project&lt;/strong&gt;. In addition to the top-level files, the actual client code is located in the &lt;code&gt;src&lt;/code&gt; folder. Since this does not fit well with the structure of Java applications, we move this code to &lt;code&gt;src/main/webapp&lt;/code&gt; instead. We keep the &lt;code&gt;.gitignore&lt;/code&gt; of our Spring application and only add the line &lt;code&gt;/node_modules&lt;/code&gt; there.&lt;/p&gt;

&lt;p&gt;We can then write the new folder name into our &lt;code&gt;angular.json&lt;/code&gt; and also adjust the target folders - for a Maven project this is &lt;code&gt;target/classes/static&lt;/code&gt;, whereas for Gradle it is &lt;code&gt;build/resources/main/static&lt;/code&gt;. We should also set the browser setting to &lt;code&gt;""&lt;/code&gt; so that we don't get an extra folder on top.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"projects"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"angularclient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"projectType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schematics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@schematics/angular:component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scss"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sourceRoot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/main/webapp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"architect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"builder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@angular-devkit/build-angular:application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"outputPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"base"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./target/classes/static"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"browser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/main/webapp/index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/main/webapp/main.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"polyfills"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="s2"&gt;"zone.js"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"tsConfig"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsconfig.app.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"inlineStyleLanguage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scss"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"assets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="s2"&gt;"src/main/webapp/favicon.ico"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"glob"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"**/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"src/main/webapp/public"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"styles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="s2"&gt;"src/main/webapp/styles.scss"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Adjusting our client configuration&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With this setup, our client should be ready to run again in its new home. We install the dependencies once and then start the development server. Our application should then be available at &lt;code&gt;http://localhost:4200&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
ng serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Spring Boot integration
&lt;/h2&gt;

&lt;p&gt;Our client application could not yet call the REST API of our backend during development, because our Spring application is reachable there under port &lt;code&gt;8080&lt;/code&gt; and a CORS error would occur. Therefore, &lt;strong&gt;we add a small configuration to our app&lt;/strong&gt; that only becomes active for the &lt;code&gt;local&lt;/code&gt; profile.This way the browser will no longer block our API calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@Profile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AngularLocalConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;WebMvcConfigurer&lt;/span&gt; &lt;span class="nf"&gt;corsConfigurer&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;WebMvcConfigurer&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;@Override&lt;/span&gt;
            &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;addCorsMappings&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;CorsRegistry&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/**"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;allowedMethods&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;allowedOrigins&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:4200"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;};&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Allowing API calls during development&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The only thing missing is the setup so that &lt;strong&gt;our Angular client is also accessible after the build and deployment&lt;/strong&gt;. To do this, we first add a special forward: for all requests that do not correspond to a defined exception, Angular's &lt;code&gt;index.html&lt;/code&gt; is rendered. The browser client then has the responibility of showing the correct content according to the URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Controller&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AngularForwardController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{path:^(?!api|public|swagger)[^\\.]*}/**"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;handleForward&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"forward:/"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To ensure that our Angular client is build and included in the Spring application (the jar), we use the &lt;code&gt;frontend-maven-plugin&lt;/code&gt; for Maven or the &lt;code&gt;gradle-node-plugin&lt;/code&gt; for Gradle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.github.eirslett&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;frontend-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.15.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;nodeAndNpmSetup&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;install-node-and-npm&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;npmInstall&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;npm&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;arguments&amp;gt;&lt;/span&gt;install&lt;span class="nt"&gt;&amp;lt;/arguments&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;npmRunBuild&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;npm&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;arguments&amp;gt;&lt;/span&gt;run build&lt;span class="nt"&gt;&amp;lt;/arguments&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;nodeVersion&amp;gt;&lt;/span&gt;v22.16.0&lt;span class="nt"&gt;&amp;lt;/nodeVersion&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Configuration of the frontend-maven-plugin for Angular&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s1"&gt;'com.github.node-gradle.node'&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s1"&gt;'7.1.0'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'22.16.0'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'npmRunBuild'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NpmTask&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'run'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'build'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;dependsOn&lt;/span&gt; &lt;span class="n"&gt;npmInstall&lt;/span&gt;

    &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileTree&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'node_modules'&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileTree&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'src/main/webapp'&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'angular.json'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'package.json'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tsconfig.json'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tsconfig.app.json'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildDirectory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'resources/main/static'&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;processResources&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dependsOn&lt;/span&gt; &lt;span class="n"&gt;npmRunBuild&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Integration of the Angular build step&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The quickest way to test our new settings is to use &lt;code&gt;mvnw spring-boot:run&lt;/code&gt; or &lt;code&gt;gradlew bootRun&lt;/code&gt;. This will &lt;strong&gt;completely build our Spring Boot app including the frontend and then start it&lt;/strong&gt;. Our application should now be accessible under &lt;code&gt;http://localhost:8080&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;In the Free plan of Bootify, Spring Boot prototypes can be created with Angular and Bootstrap or Tailwind CSS. &lt;strong&gt;CRUD functionality for the tables of your custom database schema&lt;/strong&gt; can be activated as well. The Professional plan includes advanced options such as Spring Security - start your new project in no time and with a strong foundation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bootify.io/pricing.html" rel="noopener noreferrer"&gt;&lt;strong&gt;» See Features and Pricing&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>angular</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>Htmx error handling in Spring Boot apps</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Fri, 25 Apr 2025 15:24:25 +0000</pubDate>
      <link>https://forem.com/tleipzig/htmx-error-handling-in-spring-boot-apps-47fm</link>
      <guid>https://forem.com/tleipzig/htmx-error-handling-in-spring-boot-apps-47fm</guid>
      <description>&lt;p&gt;Htmx is a fantastic library to simplify web application development and &lt;strong&gt;render most of the HTML directly in the backend&lt;/strong&gt;. However when making calls to the server, expected and unexpected errors can occur - how they are handled best in our Spring Boot application?&lt;/p&gt;

&lt;p&gt;Let's assume we already have our Spring Boot application with Thymeleaf and htmx prepared - &lt;a href="https://bootify.io/frontend/htmx-with-spring-boot-thymeleaf.html" rel="noopener noreferrer"&gt;more details here&lt;/a&gt;. Let's add an endpoint to the HomeController &lt;strong&gt;that throws an exception&lt;/strong&gt; - in the real world this could occur, for example, if the database is not available.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Controller&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"home/index"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/fail"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed!"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Adding an endpoint producing an "unexpected" error&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Thymeleaf template &lt;code&gt;resources/templates/error.html&lt;/code&gt; is added to the repository, which will be picked up and &lt;strong&gt;rendered by Spring Boot when an Exception occurs&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE HTML&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;xmlns:th=&lt;/span&gt;&lt;span class="s"&gt;"http://www.thymeleaf.org"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;[[${status}]] - [[${error}]]&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;[[${status}]] - [[${error}]]&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;[[#{error.message}]]&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Our very simple error template&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If we now call our endpoint directly via &lt;code&gt;http://localhost:8080/fail&lt;/code&gt; in the browser, the user sees our prepared template - as it should be. The situation however is different if the page is loaded by htmx via an AJAX request. This could happen for example if the &lt;code&gt;hx-boost="true"&lt;/code&gt; flag is set on the body tag in the page &lt;code&gt;http://localhost:8080/&lt;/code&gt; - in this case, the &lt;strong&gt;user would receive no feedback at all in the browser when clicking on the link&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are two issues we need to address here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;because AJAX requests are sent with the header &lt;code&gt;Accept: */*&lt;/code&gt;, the regular error handling of Spring Boot returns a JSON response&lt;/li&gt;
&lt;li&gt;the response has the HTTP status 500, so htmx does not perform swapping in the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can solve these points either in the backend or client part of our application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a dedicated error handling to our app
&lt;/h2&gt;

&lt;p&gt;In order to &lt;strong&gt;display the error page in the browser for htmx requests as well&lt;/strong&gt;, we can add the following controller to our application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Controller&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HtmxErrorController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;BasicErrorController&lt;/span&gt; &lt;span class="n"&gt;basicErrorController&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;HtmxErrorController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;BasicErrorController&lt;/span&gt; &lt;span class="n"&gt;basicErrorController&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;basicErrorController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;basicErrorController&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"${server.error.path:${error.path:/error}}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"HX-Request=true"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@ResponseStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ModelAndView&lt;/span&gt; &lt;span class="nf"&gt;errorHtmx&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;basicErrorController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;errorHtml&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Special controller for error output of htmx requests&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With this we have a new endpoint on the configured error path &lt;strong&gt;that outputs regular HTML with HTTP status 200&lt;/strong&gt;. By specifying the header &lt;code&gt;HX-Request=true&lt;/code&gt; it becomes exclusively active for requests by htmx. Since the result is rendered directly via &lt;code&gt;BasicErrorController&lt;/code&gt;, we get exactly the same result as on regular error pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error handling in the browser
&lt;/h2&gt;

&lt;p&gt;Alternatively, &lt;strong&gt;we can handle the error output in the client&lt;/strong&gt; without adding the controller. Here we could first customize the Accept header for htmx requests, so the regular error handling in our Spring Boot application will be applied.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;hx-boost=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;hx-headers=&lt;/span&gt;&lt;span class="s"&gt;"{&amp;amp;quot;Accept&amp;amp;quot;: &amp;amp;quot;text/html&amp;amp;quot;}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Adjusting the Accept header for htmx requests&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;However, the &lt;strong&gt;HTTP response status would still be 500&lt;/strong&gt; in this case - so htmx will not do the swapping by default (see &lt;a href="https://htmx.org/docs/#modifying_swapping_behavior_with_events" rel="noopener noreferrer"&gt;htmx docs&lt;/a&gt;). In order to change this behavior, we need to intercept the &lt;code&gt;htmx:beforeSwap&lt;/code&gt; event in our JavaScript code and set the &lt;code&gt;shouldSwap&lt;/code&gt; property to true in any case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;htmx:beforeSwap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shouldSwap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Extending our JavaScript code&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With the Bootify Builder a &lt;strong&gt;Spring Boot application with database schema and Thymeleaf frontend can be generated&lt;/strong&gt;. An option to include htmx is available in the Free plan - with this the application already has the &lt;code&gt;hx-boost="true"&lt;/code&gt; flag set and the &lt;code&gt;HtmxErrorController&lt;/code&gt; is integrated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bootify.io/quickstart.html" rel="noopener noreferrer"&gt;&lt;strong&gt;» Learn more&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Creating Test Jars in Maven for multi-module projects</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Mon, 03 Mar 2025 12:27:29 +0000</pubDate>
      <link>https://forem.com/tleipzig/creating-test-jars-in-maven-for-multi-module-projects-146c</link>
      <guid>https://forem.com/tleipzig/creating-test-jars-in-maven-for-multi-module-projects-146c</guid>
      <description>&lt;p&gt;In multi-module projects, individual modules often contain classes or resources that should also be used in modules based on them - e.g. for initializing test users. In Maven, the &lt;code&gt;maven-jar-plugin&lt;/code&gt; can be used to &lt;strong&gt;create a referenceable test library for this purpose&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Suppose we have a simple Spring Boot application with two modules "base" and "web". An integration test in "web" now needs to access a method &lt;code&gt;initTestUsers&lt;/code&gt;, which is stored inside the &lt;em&gt;"src/test/java"&lt;/em&gt; section in "base".&lt;/p&gt;

&lt;p&gt;First, the &lt;code&gt;pom.xml&lt;/code&gt; of the module "base" must be extended to provide a jar of the test classes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.plugins&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-jar-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.4.2&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;test-jar&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Adding maven-jar-plugin to the module providing the test data&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now &lt;strong&gt;the new library can be referenced&lt;/strong&gt; from "web". The scope "test" makes it available for testing only, so it will not be included in the final "fat jar" of our Spring Boot executable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;${project.groupId}&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;base&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;classifier&amp;gt;&lt;/span&gt;tests&lt;span class="nt"&gt;&amp;lt;/classifier&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;type&amp;gt;&lt;/span&gt;test-jar&lt;span class="nt"&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${project.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;test&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Referencing the new test jar&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Using Bootify, &lt;strong&gt;prototypes for Spring Boot applications can be created within minutes&lt;/strong&gt; - develop a concept within the team first and then directly focus on the business logic. In the Professional plan, the multi-module option is available, which automatically sets up the &lt;code&gt;maven-jar-plugin&lt;/code&gt; for Maven projects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bootify.io/quickstart.html" rel="noopener noreferrer"&gt;&lt;strong&gt;» Learn more&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>maven</category>
      <category>testing</category>
    </item>
    <item>
      <title>Form Login with Spring Boot and Thymeleaf</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Thu, 25 Jul 2024 09:05:18 +0000</pubDate>
      <link>https://forem.com/tleipzig/form-login-with-spring-boot-and-thymeleaf-3boc</link>
      <guid>https://forem.com/tleipzig/form-login-with-spring-boot-and-thymeleaf-3boc</guid>
      <description>&lt;p&gt;The form-based login is often the &lt;strong&gt;first choice to protect the web frontend of a Spring Boot application&lt;/strong&gt;. It ensures that certain areas of our application are only accessible once a user has authenticated himself with a username and password, and this status is stored in the session. What are the steps required to add form based login to our Spring Boot application?&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a simple application
&lt;/h2&gt;

&lt;p&gt;First, we use the &lt;strong&gt;Bootify Builder to create a simple Spring Boot application&lt;/strong&gt; in the current version &lt;code&gt;3.3.2&lt;/code&gt; - to do this, we simply click on &lt;a href="https://bootify.io/" rel="noopener noreferrer"&gt;open project&lt;/a&gt;. There we select &lt;code&gt;Thymeleaf + Bootstrap&lt;/code&gt; as frontend stack - Thymeleaf is the most used template engine of Spring Boot and allows server-side rendering. Bootstrap will be integrated into our app as a WebJar. Select any database which you would like to connect to - an embedded database will do for now as well.&lt;/p&gt;

&lt;p&gt;In the &lt;em&gt;Entities Tab&lt;/em&gt; we create the tables &lt;code&gt;User&lt;/code&gt; as well as &lt;code&gt;TodoList&lt;/code&gt;, and connect them with an &lt;code&gt;N:1&lt;/code&gt; relation. For the &lt;strong&gt;TodoList we activate the CRUD option for the frontend&lt;/strong&gt; - this will be the area we secure with Spring Security afterwards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3-eu-central-1.amazonaws.com%2Fbootify-prod%2Fext%2Fimg%2FformLogin%2FsimpleSchemaPreview.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3-eu-central-1.amazonaws.com%2Fbootify-prod%2Fext%2Fimg%2FformLogin%2FsimpleSchemaPreview.png" title="Preview of our very simple database schema"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;  Preview of our very simple database schema&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now we can already download the finished application and import it into our favorite IDE.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3-eu-central-1.amazonaws.com%2Fbootify-prod%2Fext%2Fimg%2FformLogin%2FintellijAfterImport.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3-eu-central-1.amazonaws.com%2Fbootify-prod%2Fext%2Fimg%2FformLogin%2FintellijAfterImport.png" title="The first version of our application in IntelliJ"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;  The first version of our application in IntelliJ&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Configuration of Spring Security
&lt;/h2&gt;

&lt;p&gt;The form-based login is provided with the help of Spring Security. So we first need the relevant dependencies, which we add to our &lt;code&gt;build.gradle&lt;/code&gt; or &lt;code&gt;pom.xml&lt;/code&gt; respectively.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'org.springframework.boot:spring-boot-starter-security'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The module &lt;code&gt;spring-boot-starter-security&lt;/code&gt; integrates Spring Security. With &lt;code&gt;thymeleaf-extras-springsecurity6&lt;/code&gt; a little helper is included which provides the authentication state in our Thymeleaf templates - more about that later.&lt;/p&gt;

&lt;p&gt;With this we can &lt;strong&gt;already provide the central security configuration&lt;/strong&gt; - here directly in our final version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@EnableMethodSecurity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prePostEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpSecurityConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;PasswordEncoder&lt;/span&gt; &lt;span class="nf"&gt;passwordEncoder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PasswordEncoderFactories&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createDelegatingPasswordEncoder&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;AuthenticationManager&lt;/span&gt; &lt;span class="nf"&gt;authenticationManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;AuthenticationConfiguration&lt;/span&gt; &lt;span class="n"&gt;authenticationConfiguration&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;authenticationConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAuthenticationManager&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SecurityFilterChain&lt;/span&gt; &lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;HttpSecurity&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;withDefaults&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;csrf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;withDefaults&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authorizeHttpRequests&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authorize&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;authorize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;anyRequest&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;permitAll&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;formLogin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loginPage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usernameParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;failureUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/login?loginError=true"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;logout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logout&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logout&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;logoutSuccessUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/login?logoutSuccess=true"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deleteCookies&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"JSESSIONID"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exceptionHandling&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authenticationEntryPoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LoginUrlAuthenticationEntryPoint&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/login?loginRequired=true"&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Our configuration for the form login&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Theoretically, Spring Security can provide the login form with less configuration, but this would lack the design and some messages which we would like to present to the user. But let's go through the config first.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BCryptPasswordEncoder&lt;/code&gt; is standard nowadays &lt;strong&gt;to store the hash of a password together with its individual salt&lt;/strong&gt; inside a single field. If we don't have a legacy requirement, we should use this one. Also, we provide the &lt;code&gt;AuthenticationManager&lt;/code&gt; as a bean to be able to integrate it into other services.&lt;/p&gt;

&lt;p&gt;As a third bean we create the &lt;code&gt;SecurityFilterChain&lt;/code&gt; as this is the required approach since Spring 3.0. We should configure both CORS and CSRF properly to close the corresponding attack vectors. The default configuration is usually sufficient for this.&lt;/p&gt;

&lt;p&gt;At our config class we have placed the annotation &lt;code&gt;@EnableMethodSecurity&lt;/code&gt; and will protect the desired controller endpoints with &lt;code&gt;@PreAuthorize(...)&lt;/code&gt; later on. Therefore &lt;strong&gt;we allow access to the whole application&lt;/strong&gt; with &lt;code&gt;permitAll()&lt;/code&gt;. Without the annotation based security, we should configure the paths to the protected resources at this place as well.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;formLogin()&lt;/code&gt; and the &lt;code&gt;logout()&lt;/code&gt; methods are customized for our subsequent controller so that we can &lt;strong&gt;always display an appropriate message to the user&lt;/strong&gt;. Spring Security automatically provides an endpoint for the login where username and password can be submitted to via POST request. Here we change the name of the username field to &lt;code&gt;"email"&lt;/code&gt;. The logout is modified to redirect back to the login page with a parameter afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading users from our database
&lt;/h2&gt;

&lt;p&gt;To load the users from the already created table, we need to provide an implementation of &lt;code&gt;UserDetailsService&lt;/code&gt; as a bean - it will be &lt;strong&gt;automatically found and used as a user source&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpUserDetailsService&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;UserDetailsService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;HttpUserDetailsService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;userRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;HttpUserDetails&lt;/span&gt; &lt;span class="nf"&gt;loadUserByUsername&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByEmailIgnoreCase&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user not found: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UsernameNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" not found"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SimpleGrantedAuthority&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;roles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;singletonList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SimpleGrantedAuthority&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ROLE_USER"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpUserDetails&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getHash&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;roles&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  UserDetailsService implementation automatically used as the user source&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In our repository we should add a method &lt;code&gt;User findByEmailIgnoreCase(String email)&lt;/code&gt; to execute a search query against the database - ignoring upper/lower case allows the user small mistakes when writing their email. The role here is always &lt;code&gt;ROLE_USER&lt;/code&gt; for each user. Since we don't have a registration endpoint available at this point, we can add a simple data loader along with our application for now. The profile &lt;code&gt;"local"&lt;/code&gt; is required for it to become active.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="nd"&gt;@Profile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserLoader&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ApplicationRunner&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;PasswordEncoder&lt;/span&gt; &lt;span class="n"&gt;passwordEncoder&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;UserLoader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;PasswordEncoder&lt;/span&gt; &lt;span class="n"&gt;passwordEncoder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;userRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;passwordEncoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;passwordEncoder&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ApplicationArguments&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setEmail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test@test.com"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setHash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;passwordEncoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;encode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"testtest"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Helper class to initialize a first user locally&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add login controller
&lt;/h2&gt;

&lt;p&gt;With this we can already add the &lt;code&gt;LoginController&lt;/code&gt;. Since the POST endpoint is automatically provided by Spring Security, a GET endpoint is sufficient here to show the template to the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Controller&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthenticationController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"loginRequired"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="n"&gt;loginRequired&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"loginError"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="n"&gt;loginError&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"logoutSuccess"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="n"&gt;logoutSuccess&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"authentication"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AuthenticationRequest&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loginRequired&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TRUE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MSG_INFO&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;WebUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"authentication.login.required"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loginError&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TRUE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MSG_ERROR&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;WebUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"authentication.login.error"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logoutSuccess&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TRUE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MSG_INFO&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;WebUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"authentication.logout.success"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"authentication/login"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Backend for rendering the login page&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;request parameters that we had already specified in our security configuration&lt;/strong&gt; are converted to corresponding messages here. In our simple application from Bootify the corresponding helpers are already included. Here we also need the &lt;code&gt;AuthenticationRequest&lt;/code&gt; object with getters and setters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthenticationRequest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@NotNull&lt;/span&gt;
    &lt;span class="nd"&gt;@Size&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@NotNull&lt;/span&gt;
    &lt;span class="nd"&gt;@Size&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The corresponding template for our controller could then look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE HTML&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;xmlns:th=&lt;/span&gt;&lt;span class="s"&gt;"http://www.thymeleaf.org"&lt;/span&gt; &lt;span class="na"&gt;xmlns:layout=&lt;/span&gt;&lt;span class="s"&gt;"http://www.ultraq.net.nz/thymeleaf/layout"&lt;/span&gt;
        &lt;span class="na"&gt;layout:decorate=&lt;/span&gt;&lt;span class="s"&gt;"~{layout}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;[[#{authentication.login.headline}]]&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;layout:fragment=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;[[#{authentication.login.headline}]]&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;th:replace=&lt;/span&gt;&lt;span class="s"&gt;"~{fragments/forms::globalErrors('authentication')}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;th:action=&lt;/span&gt;&lt;span class="s"&gt;"${requestUri}"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;th:replace=&lt;/span&gt;&lt;span class="s"&gt;"~{fragments/forms::inputRow(object='authentication', field='email')}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;th:replace=&lt;/span&gt;&lt;span class="s"&gt;"~{fragments/forms::inputRow(object='authentication', field='password', type='password')}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;th:value=&lt;/span&gt;&lt;span class="s"&gt;"#{authentication.login.headline}"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary mt-4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As Thymeleaf doesn't allow direct access to request object anymore, we're providing the &lt;code&gt;requestUri&lt;/code&gt; in the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ModelAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"requestUri"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getRequestServletPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestURI&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;_  Providing the requestUri - as part of the AuthenticationController or a general ControllerAdvice _&lt;/p&gt;

&lt;p&gt;With this template we send a POST request to the &lt;code&gt;/login&lt;/code&gt; endpoint. The INFO or ERROR messages are automatically displayed by the layout. All used messages have to be present in our &lt;code&gt;messages.properties&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;authentication.login.headline&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Login&lt;/span&gt;
&lt;span class="py"&gt;authentication.email.label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Email&lt;/span&gt;
&lt;span class="py"&gt;authentication.password.label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Password&lt;/span&gt;
&lt;span class="py"&gt;authentication.login.required&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Please login to access this area.&lt;/span&gt;
&lt;span class="py"&gt;authentication.login.error&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Your login was not successful - please try again.&lt;/span&gt;
&lt;span class="py"&gt;authentication.logout.success&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Your logout was successful.&lt;/span&gt;
&lt;span class="py"&gt;navigation.login&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Login&lt;/span&gt;
&lt;span class="py"&gt;navigation.logout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Logout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last we can extend our &lt;code&gt;layout.html&lt;/code&gt;. With this we also always show a login / logout link in the header. Spring Security also automatically provides a &lt;code&gt;/logout&lt;/code&gt; endpoint, but we have to address it via POST.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="na"&gt;xmlns:th=&lt;/span&gt;&lt;span class="s"&gt;"http://www.thymeleaf.org"&lt;/span&gt; &lt;span class="na"&gt;xmlns:layout=&lt;/span&gt;&lt;span class="s"&gt;"http://www.ultraq.net.nz/thymeleaf/layout"&lt;/span&gt;
            &lt;span class="na"&gt;xmlns:sec=&lt;/span&gt;&lt;span class="s"&gt;"http://www.thymeleaf.org/extras/spring-security"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;    
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;sec:authorize=&lt;/span&gt;&lt;span class="s"&gt;"!isAuthenticated()"&lt;/span&gt; &lt;span class="na"&gt;th:href=&lt;/span&gt;&lt;span class="s"&gt;"@{/login}"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;[[#{navigation.login}]]&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;sec:authorize=&lt;/span&gt;&lt;span class="s"&gt;"isAuthenticated()"&lt;/span&gt; &lt;span class="na"&gt;th:action=&lt;/span&gt;&lt;span class="s"&gt;"@{/logout}"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"nav-link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;th:value=&lt;/span&gt;&lt;span class="s"&gt;"#{navigation.logout}"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"unset"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Adding login / logout links to our layout&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;html&lt;/code&gt; tag we've extended the namespace to use the helpers from the &lt;code&gt;thymeleaf-extras-springsecurity6&lt;/code&gt; module. As a final step we only need to add the annotation &lt;code&gt;@PreAuthorize("hasAuthority('ROLE_USER')")&lt;/code&gt; at our &lt;code&gt;TodoListController&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting our application
&lt;/h2&gt;

&lt;p&gt;With this &lt;strong&gt;we have all needed pieces of our puzzle together!&lt;/strong&gt; Now we start our application and when we want to see the todo lists, we should be redirected to the login page. Here we can log in with &lt;code&gt;test@test.com&lt;/code&gt; / &lt;code&gt;testtest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3-eu-central-1.amazonaws.com%2Fbootify-prod%2Fext%2Fimg%2FformLogin%2FloginRedirect.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3-eu-central-1.amazonaws.com%2Fbootify-prod%2Fext%2Fimg%2FformLogin%2FloginRedirect.png" title="Automatic redirect to the login"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;  Automatic redirect to the login&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the Free plan of Bootify, &lt;strong&gt;Spring Boot prototypes with its own database schema, REST API and frontend&lt;/strong&gt; can be generated. In the Professional plan, among other things, &lt;strong&gt;Spring Security with the form-based login is available&lt;/strong&gt; to generate the setup described here - exactly matching the created database and the selected settings. A registration endpoint and role source can be specified as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bootify.io/pricing.html" rel="noopener noreferrer"&gt;&lt;strong&gt;» See Features and Pricing&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>security</category>
      <category>java</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Spring Boot with Thymeleaf and Bootstrap</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Mon, 13 May 2024 09:37:58 +0000</pubDate>
      <link>https://forem.com/tleipzig/spring-boot-with-thymeleaf-and-bootstrap-59ml</link>
      <guid>https://forem.com/tleipzig/spring-boot-with-thymeleaf-and-bootstrap-59ml</guid>
      <description>&lt;p&gt;Thymeleaf and Bootstrap are &lt;strong&gt;powerful technologies to develop modern web applications&lt;/strong&gt;. In this article, we will show the steps to add Thymeleaf and Bootstrap to our Spring Boot application.&lt;/p&gt;

&lt;p&gt;The complete setup can also be created directly in the Bootify Builder. Just open a project with &lt;a href="https://bootify.io/"&gt;open project&lt;/a&gt;, choose your preferences and select &lt;code&gt;Thymeleaf + Bootstrap&lt;/code&gt; as frontend stack. If you don't select a frontend, all the following steps can be followed manually. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Themeleaf to our app
&lt;/h2&gt;

&lt;p&gt;Thymeleaf is the most used template engine for Spring Boot. Since the &lt;strong&gt;templates are written in valid HTML code&lt;/strong&gt;, they can be opened without server-side rendering as well. To setup the engine and all recommended utilities, the following two dependencies are added to our application first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'org.springframework.boot:spring-boot-starter-thymeleaf'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Adding new dependencies for Thymeleaf&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The module &lt;code&gt;spring-boot-starter-thymeleaf&lt;/code&gt; will automatically integrate the template engine - in the following controller we can already see how this works. The &lt;code&gt;thymeleaf-layout-dialect&lt;/code&gt; is recommended for most productive applications, in order that &lt;strong&gt;several pages can use the same layout&lt;/strong&gt;. We don't need to provide any version here, as they are part of the dependency management plugin of Spring Boot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Controller&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/todo"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"todos"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;todoService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findAll&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"todo/list"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  A simple MVC controller&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Our simple endpoint just shows a list of todos. For this &lt;strong&gt;all current todos are loaded from the service first and added to the model&lt;/strong&gt;. With &lt;code&gt;"todo/list"&lt;/code&gt; the template from &lt;code&gt;src/main/resources/templates/todo/list.html&lt;/code&gt; is loaded. Unless we configure it otherwise, the &lt;code&gt;template&lt;/code&gt; folder is the default one from which the templates are picked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE HTML&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;xmlns:th=&lt;/span&gt;&lt;span class="s"&gt;"http://www.thymeleaf.org"&lt;/span&gt; &lt;span class="na"&gt;xmlns:layout=&lt;/span&gt;&lt;span class="s"&gt;"http://www.ultraq.net.nz/thymeleaf/layout"&lt;/span&gt;
        &lt;span class="na"&gt;layout:decorate=&lt;/span&gt;&lt;span class="s"&gt;"~{layout}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;[[#{todoList.list.headline}]]&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;layout:fragment=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;th:if=&lt;/span&gt;&lt;span class="s"&gt;"${todoLists.empty}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;[[#{todoList.list.empty}]]&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;th:if=&lt;/span&gt;&lt;span class="s"&gt;"${!todoLists.empty}"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"table-responsive"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"table table-striped table-hover align-middle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"col"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;[[#{todoList.name.label}]]&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- --&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; &lt;span class="na"&gt;th:each=&lt;/span&gt;&lt;span class="s"&gt;"todo : ${todos}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;[[${todo.name}]]&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"float-end text-nowrap"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;th:href=&lt;/span&gt;&lt;span class="s"&gt;"@{/todos/edit/{id}(id=${todo.id})}"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-sm btn-info"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;[[#{todo.list.edit}]]&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  A simple Thymeleaf Template&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After adding our &lt;code&gt;list.html&lt;/code&gt; template, we are already using Thymeleaf and Bootstrap classes successfully - the only missing layout will be shown in a moment. To enable real hot reloading during development, it is not enough to switch off template caching. How to read the HTML files directly from the file system is described &lt;a href="https://bootify.io/frontend/thymeleaf-templates-hot-reload.html"&gt;in this article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrap integration using WebJars
&lt;/h2&gt;

&lt;p&gt;Bootstrap is currently the most popular CSS framework and can be &lt;strong&gt;integrated directly into our Spring Boot app using WebJars&lt;/strong&gt;. For this we need another dependency first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'org.webjars:bootstrap:5.3.3'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now include the JavaScript and CSS files for Bootstrap in our &lt;code&gt;layout.html&lt;/code&gt; as follows. Our own JS or CSS can be provided additionally in &lt;code&gt;src/main/resources/static/js&lt;/code&gt; and &lt;code&gt;src/main/resources/static/css&lt;/code&gt; if needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="na"&gt;xmlns:th=&lt;/span&gt;&lt;span class="s"&gt;"http://www.thymeleaf.org"&lt;/span&gt; &lt;span class="na"&gt;xmlns:layout=&lt;/span&gt;&lt;span class="s"&gt;"http://www.ultraq.net.nz/thymeleaf/layout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;th:href=&lt;/span&gt;&lt;span class="s"&gt;"@{/webjars/bootstrap/5.3.0/css/bootstrap.min.css}"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;th:href=&lt;/span&gt;&lt;span class="s"&gt;"@{/css/app.css}"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;th:src=&lt;/span&gt;&lt;span class="s"&gt;"@{/webjars/bootstrap/5.3.0/js/bootstrap.bundle.min.js}"&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;th:src=&lt;/span&gt;&lt;span class="s"&gt;"@{/js/app.js}"&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"my-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;layout:fragment=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Integration of Bootstrap files in their minimized versions&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrap integration using Webpack
&lt;/h2&gt;

&lt;p&gt;Alternatively, Bootstrap can also be integrated using Webpack to define &lt;strong&gt;more complex rules for how CSS and JS should be provided to the frontend&lt;/strong&gt;. If Webpack is already prepared, only one command is needed to add Bootstrap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i --save bootstrap @popperjs/core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, since the Webpack setup with npm and DevServer requires a lot of explanation, please check the article about the &lt;a href="https://bootify.io/frontend/webpack-spring-boot.html"&gt;setup of Spring Boot with Webpack&lt;/a&gt;. Using Webpack offers many advantages - but also comes with the cost of a higher integration effort.&lt;/p&gt;

&lt;p&gt;In the Free plan of Bootify, &lt;strong&gt;Spring Boot application including a frontend stack can be initialized&lt;/strong&gt; - directly in the browser, without registration. Thymeleaf with Bootstrap is available there, either directly via WebJars or via Webpack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bootify.io/"&gt;&lt;strong&gt;» Start Project on Bootify.io&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
 &lt;/p&gt;

&lt;h3&gt;
  
  
  Further readings
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.thymeleaf.org/documentation.html"&gt;Thymeleaf documentation&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://getbootstrap.com"&gt;Bootstrap&lt;/a&gt;  &lt;/p&gt;

</description>
      <category>springboot</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Uppercase table names in Spring Boot</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Mon, 25 Mar 2024 15:37:25 +0000</pubDate>
      <link>https://forem.com/tleipzig/uppercase-table-names-in-spring-boot-40g1</link>
      <guid>https://forem.com/tleipzig/uppercase-table-names-in-spring-boot-40g1</guid>
      <description>&lt;p&gt;In Spring Boot / Hibernate, the table name is derived from the entity name. What must our naming strategy look like &lt;strong&gt;to translate all table names to uppercase?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without further configuration, the physical name of the entity is converted to lowercase with underscores - so our class &lt;code&gt;LineItem&lt;/code&gt; becomes the table &lt;code&gt;line_item&lt;/code&gt;. We could try to store the adjusted name at each table with &lt;code&gt;@Table(name = "LINE_ITEM")&lt;/code&gt; to overwrite the physical name - but would still need to change the naming strategy, because otherwise it would be changed back to lowercase after all.&lt;/p&gt;

&lt;p&gt;Therefore the best approach is to provide the &lt;strong&gt;transformation for all table names with a dedicated naming strategy&lt;/strong&gt;. Additional information within the &lt;code&gt;@Table&lt;/code&gt; annotation is no longer necessary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CamelCaseToUppercaseTablesNamingStrategy&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;CamelCaseToUnderscoresNamingStrategy&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Identifier&lt;/span&gt; &lt;span class="nf"&gt;adjustName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Identifier&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;adjustedName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toUpperCase&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Identifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adjustedName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Identifier&lt;/span&gt; &lt;span class="nf"&gt;toPhysicalTableName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Identifier&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;JdbcEnvironment&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;adjustName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toPhysicalTableName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Customization of all table names using a custom naming strategy&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Our naming strategy extends from &lt;code&gt;CamelCaseToUnderscoresNamingStrategy&lt;/code&gt; so that all other methods (e.g. for the field names) still apply. The table name is first changed to lowercase with underscores, &lt;strong&gt;and then altered to uppercase&lt;/strong&gt; in &lt;code&gt;adjustName&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All adjusted identifiers are automatically created with &lt;code&gt;quoted = true&lt;/code&gt;. This prevents the database from changing the table name and the name is taken over exactly. Finally only one entry in our &lt;code&gt;application.yml&lt;/code&gt; / &lt;code&gt;application.properties&lt;/code&gt; is missing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;spring.jpa.hibernate.naming.physical-strategy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;io.bootify.my_app.config.CamelCaseToUppercaseTablesNamingStrategy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;With this, Hibernate / Spring Data translates all physical names to uppercase with underscores.&lt;/strong&gt; We could also override the &lt;code&gt;toColumnTableName&lt;/code&gt; method if we also want to translate the field names as well. With a little adjustment, camel case would also be possible as output.&lt;/p&gt;

&lt;p&gt;Bootify allows to create any custom database schema for your next Spring Boot application - directly in the browser, without registration. &lt;strong&gt;In the Professional plan, four additional Naming Strategies are available for selection&lt;/strong&gt; and directly provide the configured, executable application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bootify.io/pricing.html"&gt;&lt;strong&gt;» See Features and Pricing&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>jpa</category>
      <category>hibernate</category>
      <category>java</category>
    </item>
    <item>
      <title>How to create a Kotlin Multi-Module Project</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Fri, 17 Nov 2023 12:18:27 +0000</pubDate>
      <link>https://forem.com/tleipzig/how-to-create-a-kotlin-multi-module-project-55da</link>
      <guid>https://forem.com/tleipzig/how-to-create-a-kotlin-multi-module-project-55da</guid>
      <description>&lt;p&gt;&lt;strong&gt;Spring Boot and Kotlin are a powerful combination&lt;/strong&gt; for creating modern web applications. For Kotlin, we usually use Gradle with the Kotlin DSL developed for it. How can we turn such a project into a multi-module project?&lt;/p&gt;

&lt;p&gt;Multi-module projects allow a monolith to be broken down into distinct modules without the need for the complex infrastructure required for microservices. The modules are &lt;strong&gt;not only separate folders similar to packages, but also form a dependency graph&lt;/strong&gt; - thus circular connections are not possible. In the end, each module is compiled into a jar, and all jar's together form the executable Spring Boot fat jar.&lt;/p&gt;

&lt;p&gt;The correct partitioning of the modules is crucial for an architecture that supports maintainability of the app and collaboration within the team in the long run. A few tips on this can be found in &lt;a href="https://bootify.io/multi-module/best-practices-for-spring-boot-multi-module.html"&gt;best practices for multi-module projects&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Splitting the classes and resources into the modules
&lt;/h2&gt;

&lt;p&gt;We can create a simple Kotlin project in the current Spring Boot version &lt;code&gt;3.1.5&lt;/code&gt; directly in the Bootify Builder. With &lt;a href="https://bootify.io/"&gt;open project&lt;/a&gt; we launch a project and select Kotlin as language. Gradle is already preset, and the database we can set to None for now. With this we can already explore and download the source code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NFdzfzSP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://s3-eu-central-1.amazonaws.com/bootify-prod/ext/img/multiModule/kotlinProjectGradle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NFdzfzSP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://s3-eu-central-1.amazonaws.com/bootify-prod/ext/img/multiModule/kotlinProjectGradle.png" alt="" title="Initial version of the build.gradle.kts" width="800" height="347"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;  Initial version of the build.gradle.kts&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before we customize the build file, we first prepare the two modules &lt;code&gt;kotlin-web&lt;/code&gt; and &lt;code&gt;kotlin-base&lt;/code&gt; and &lt;strong&gt;create two folders with exactly these names&lt;/strong&gt;. For the web module we move only the &lt;code&gt;HomeController&lt;/code&gt; to the new path &lt;code&gt;./kotlin-web/src/main/kotlin/io/bootify/kotlin/controller&lt;/code&gt;. All other classes and resources are relevant for the whole application, so we move the whole &lt;code&gt;src&lt;/code&gt; folder to &lt;code&gt;./kotlin-base&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'kotlin-base'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'kotlin-web'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Adding the new settings.gradle file&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We also need a new file that provides a list of all our modules to Gradle. With this, the only thing missing at last is the customization of the &lt;code&gt;build.gradle.kts&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizing the build file
&lt;/h2&gt;

&lt;p&gt;First we prepare the plugins. The version of Kotlin should always be derived from the Spring Boot version, &lt;strong&gt;as this ensures the highest possible compatibility&lt;/strong&gt; - the newest Kotlin version is not tested with Spring Boot yet. The &lt;code&gt;allprojects&lt;/code&gt; part specifies a few global settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.jetbrains.kotlin.gradle.tasks.KotlinCompile&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.gradle.dsl.SpringBootExtension&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.gradle.tasks.run.BootRun&lt;/span&gt;

&lt;span class="k"&gt;buildscript&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;repositories&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;mavenCentral&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"org.springframework.boot"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s2"&gt;"3.1.5"&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"io.spring.dependency-management"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s2"&gt;"1.1.3"&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

    &lt;span class="n"&gt;kotlin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"jvm"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s2"&gt;"1.8.22"&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="n"&gt;kotlin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"plugin.spring"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s2"&gt;"1.8.22"&lt;/span&gt; &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;allprojects&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JavaCompile&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sourceCompatibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"17"&lt;/span&gt;
        &lt;span class="n"&gt;targetCompatibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"17"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;KotlinCompile&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;kotlinOptions&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;freeCompilerArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-Xjsr305=strict"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;jvmTarget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"17"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  First part of our extended build file&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the following, &lt;strong&gt;the prepared plugins are applied to all submodules&lt;/strong&gt;. Some settings like the location of the Spring Boot BOM and the main class are specified as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="k"&gt;subprojects&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"java-library"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"io.spring.dependency-management"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"org.jetbrains.kotlin.jvm"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"org.jetbrains.kotlin.plugin.spring"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"org.springframework.boot"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;useJUnitPlatform&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;repositories&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;mavenCentral&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DependencyManagementExtension&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;imports&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mavenBom&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;springframework&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gradle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SpringBootPlugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BOM_COORDINATES&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;apply&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"projectName"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"${extra.get("&lt;/span&gt;&lt;span class="n"&gt;rootProjectName&lt;/span&gt;&lt;span class="s2"&gt;")}-$project.name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SpringBootExtension&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;mainClass&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"io.bootify.kotlin.base.KotlinApplicationKt"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getByName&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BootRun&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s2"&gt;"bootRun"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Second part of our build file&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the last part &lt;strong&gt;we configure our actual modules&lt;/strong&gt;. Here &lt;code&gt;kotlin-base&lt;/code&gt; is referenced by &lt;code&gt;kotlin-web&lt;/code&gt;. By integrating further depedencies as &lt;code&gt;api&lt;/code&gt; in &lt;code&gt;kotlin-base&lt;/code&gt; they are visible in &lt;code&gt;kotlin-web&lt;/code&gt; as well. With &lt;code&gt;implementation&lt;/code&gt; a depedency would be "invisible" within &lt;code&gt;kotlin-web&lt;/code&gt; during development, even if eventually all dependencies end up together in the fat jar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;":kotlin-web"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;apply&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"application"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getByName&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BootRun&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s2"&gt;"bootRun"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SPRING_PROFILES_ACTIVE"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SPRING_PROFILES_ACTIVE"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s2"&gt;"local"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;workingDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rootProject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;projectDir&lt;/span&gt;
        &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;configurations&lt;/span&gt;

        &lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;":kotlin-base"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;":kotlin-base"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;configurations&lt;/span&gt;

        &lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"org.springframework.boot:spring-boot-starter-web"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"org.springframework.boot:spring-boot-starter-validation"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"org.jetbrains.kotlin:kotlin-reflect"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"org.jetbrains.kotlin:kotlin-stdlib-jdk8"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Last part of our build file&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;bootRun&lt;/code&gt; task is only enabled for our &lt;code&gt;kotlin-web&lt;/code&gt; module and uses the root module as a working directory. This way all required files like &lt;code&gt;docker-compose.yml&lt;/code&gt; can be picked up from there. &lt;/p&gt;

&lt;p&gt;With this &lt;strong&gt;we have already transformed our project into a multi-module project!&lt;/strong&gt; With &lt;code&gt;gradlew clean build&lt;/code&gt; we can build our application as usual - which will compile and test each modules one after the other according to their dependency path.&lt;/p&gt;

&lt;p&gt;Bootify offers an option in the Professional plan to create a multi-module project. &lt;strong&gt;This creates the structure described here&lt;/strong&gt;, matching the other settings selected. In addition, integration tests are available and custom modules can be added, which are then already integrated into the build process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bootify.io/pricing.html"&gt;&lt;strong&gt;» See Features and Pricing&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>kotlin</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Custom Template for your Spring Boot App</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Mon, 23 Oct 2023 15:54:54 +0000</pubDate>
      <link>https://forem.com/tleipzig/custom-template-for-your-spring-boot-app-e1p</link>
      <guid>https://forem.com/tleipzig/custom-template-for-your-spring-boot-app-e1p</guid>
      <description>&lt;p&gt;Bootify is a tool for &lt;strong&gt;creating your individual template for a Spring Boot application&lt;/strong&gt;. Numerous features are available, which are added to the template exactly as required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preferences and database schema
&lt;/h2&gt;

&lt;p&gt;In the &lt;em&gt;General Tab&lt;/em&gt;, general specifications can be made first: Java or Kotlin, enable Lombok, choose a frontend stack and a database, and so on. Select between technical or domain oriented package structure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OIkPFgbV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://s3-eu-central-1.amazonaws.com/bootify-prod/ext/img/appTemplate/developerPreferences.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OIkPFgbV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://s3-eu-central-1.amazonaws.com/bootify-prod/ext/img/appTemplate/developerPreferences.png" alt="" title="Choice of database and preferences" width="689" height="429"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;  Choice of database and preferences&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;em&gt;Entity tab&lt;/em&gt; the database schema for the selected database can then be created. This will generate the JPA / Hibernate entities with the annotations for fields and relations from the schema. If the &lt;strong&gt;CRUD option has been activated for the REST API or the frontend&lt;/strong&gt;, additional services and controllers are available.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eP5FSENF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://s3-eu-central-1.amazonaws.com/bootify-prod/ext/img/appTemplate/editEntity.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eP5FSENF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://s3-eu-central-1.amazonaws.com/bootify-prod/ext/img/appTemplate/editEntity.png" alt="" title="Editing an entity" width="677" height="514"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;  Editing an entity&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With the Professional plan, extended features for your Spring Boot template are available. These include &lt;strong&gt;Spring Security&lt;/strong&gt; (JWT, form-based login or Keycloak via OIDC), &lt;strong&gt;integration tests including Testcontainers&lt;/strong&gt;, pagination and &lt;a href="https://bootify.io/quickstart-pro.html"&gt;much more&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizable Templates in the DevOps Plan
&lt;/h2&gt;

&lt;p&gt;In the DevOps plan &lt;strong&gt;the Bootify Builder itself can be customized&lt;/strong&gt;. This allows to configure the options in such a way that the generated application always corresponds to the environment of the company. Any custom files can be added to the template. Special hooks are available to extend the build files at the required points.&lt;/p&gt;

&lt;p&gt;With Bootify, the Developer Experience is in the focus: the created Spring Boot template saves a lot of time by providing components that are needed over and over again. At the same time, the &lt;strong&gt;code is free of external dependencies and avoids unnecessary overhead&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bootify.io/pricing.html"&gt;&lt;strong&gt;» See Features and Pricing&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>devops</category>
      <category>scaffolding</category>
      <category>java</category>
    </item>
    <item>
      <title>Using htmx with Spring Boot and Thymeleaf</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Tue, 15 Aug 2023 12:09:18 +0000</pubDate>
      <link>https://forem.com/tleipzig/using-htmx-with-spring-boot-and-thymeleaf-4ph</link>
      <guid>https://forem.com/tleipzig/using-htmx-with-spring-boot-and-thymeleaf-4ph</guid>
      <description>&lt;p&gt;Htmx allows &lt;strong&gt;building complex web applications that feel like an SPA&lt;/strong&gt; without relying on Angular or React. The client's behavior is mainly controlled by attributes within the HTML code that execute an Ajax request and integrate the result into the DOM - &lt;strong&gt;perfectly fitting for server-side rendering with Thymeleaf&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic setup
&lt;/h2&gt;

&lt;p&gt;To keep the example simple, let's setup a Spring Boot application based on Thymeleaf and Bootstrap. We use WebJars to provide the libraries for the client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="k"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'org.springframework.boot:spring-boot-starter-web'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'org.springframework.boot:spring-boot-starter-thymeleaf'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'org.webjars:bootstrap:5.3.1'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'org.webjars.npm:htmx.org:1.9.4'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;developmentOnly&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'org.springframework.boot:spring-boot-devtools'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Required dependencies for using Thymeleaf, Bootstrap and htmx&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Our controller will render our index page and already contains a second endpoint for loading additional info via AJAX later on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Controller&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"index"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/furtherInfo"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;furtherInfo&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"furtherInfo"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Controller to output our templates&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's prepare a simple template for our index page. We won't use a dedicated layout here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt; &lt;span class="na"&gt;xmlns:th=&lt;/span&gt;&lt;span class="s"&gt;"http://www.thymeleaf.org"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Homepage&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"text/html; charset=utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;th:href=&lt;/span&gt;&lt;span class="s"&gt;"@{/webjars/bootstrap/5.3.1/css/bootstrap.min.css}"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;th:src=&lt;/span&gt;&lt;span class="s"&gt;"@{/webjars/bootstrap/5.3.1/js/bootstrap.bundle.min.js}"&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;th:src=&lt;/span&gt;&lt;span class="s"&gt;"@{/webjars/htmx.org/1.9.4/dist/htmx.min.js}"&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"my-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Homepage&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-5 js-greeting"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello World!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Change greeting&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Our Thymeleaf template in resources/templates/index.html&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;How we &lt;strong&gt;create ourselves a complete project&lt;/strong&gt; is described further below. If everything is set up correctly, after starting the application we can reach our home page at &lt;code&gt;http://loclahost:8080/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Activate htmx behavior
&lt;/h2&gt;

&lt;p&gt;Without further configuration htmx is not enabling any functionality. Let's quickly add some logic to our button, which will &lt;strong&gt;call the second endpoint of our controller and replace our greeting message&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;th:hx-post=&lt;/span&gt;&lt;span class="s"&gt;"@{/furtherInfo}"&lt;/span&gt;
        &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"click"&lt;/span&gt;
        &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;".js-greeting"&lt;/span&gt;
        &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Change greeting&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Extend our button with htmx logic&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We use Thymeleaf's ability here to populate any HTML attribute - in this case we create the link to our endpoint, which will be contained in the &lt;code&gt;hx-post&lt;/code&gt; attribute. On click, &lt;strong&gt;htmx will now call the endpoint via Ajax and replace our targeted &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; element&lt;/strong&gt; with the response. A more detailed explanation of the attributes can be found in &lt;a href="https://htmx.org/docs/"&gt;the htmx documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-5 js-greeting"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello Pluto!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;  Updated greeting in furtherInfo.html&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Alternatively, we could add the &lt;code&gt;hx-boost="true"&lt;/code&gt; attribute to our body element. This will cause all links and forms to be picked up by htmx, so &lt;strong&gt;all actions are performed without reloading the browser&lt;/strong&gt; - creating an SPA-like user experience. Two possible approaches to error handling of htmx requests in Spring Boot are described &lt;a href="https://bootify.io/frontend/htmx-error-handling-in-spring-boot.html"&gt;in this article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate propotype with htmx setup
&lt;/h2&gt;

&lt;p&gt;With Bootify &lt;strong&gt;the described setup can be generated directly in the browser&lt;/strong&gt;. The frontend with htmx is available in the Free plan without registration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C60XeXGR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://s3-eu-central-1.amazonaws.com/bootify-prod/ext/img/frontend/builderHtmxSetup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C60XeXGR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://s3-eu-central-1.amazonaws.com/bootify-prod/ext/img/frontend/builderHtmxSetup.png" alt="" title="Options to generate a Spring Boot app with htmx" width="800" height="257"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;  Options to generate a Spring Boot app with htmx&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The current versions of all libraries are always used, and an individual database schema including CRUD functions can be added if required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bootify.io/"&gt;&lt;strong&gt;» Start Project on Bootify.io&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
