5.15.1 Getting Started Building Web Applications With Spring Boot and Kotlin
5.15.1 Getting Started Building Web Applications With Spring Boot and Kotlin
io/guides/tutorials/spring-boot-kotlin
1. Select "Gradle - Kotlin" or "Maven" depending on which build tool you want to use
2. Enter the following artifact coordinates: blog
3. Add the following dependencies:
◦ Spring Web
◦ Mustache
◦ Spring Data JPA
◦ H2 Database
◦ Spring Boot DevTools
4. Click "Generate Project".
The .zip file contains a standard project in the root directory, so you might want to create an empty directory before you unpack
it.
To access the wizard, go to File | New | Project, and select Spring Initializr.
Follow the steps of the wizard to use the following parameters:
• Artifact: "blog"
• Type: "Gradle - Kotlin" or "Maven"
1 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
• Language: Kotlin
• Name: "Blog"
• Dependencies: "Spring Web Starter", "Mustache", "Spring Data JPA", "H2 Database" and "Spring Boot DevTools"
Plugins
In addition to the obvious Kotlin Gradle plugin, the default configuration declares the kotlin-spring plugin which automatically
opens classes and methods (unlike in Java, the default qualifier is final in Kotlin) annotated or meta-annotated with Spring
annotations. This is useful to be able to create @Configuration or @Transactional beans without having to add the open qualifier
required by CGLIB proxies for example.
In order to be able to use Kotlin non-nullable properties with JPA, Kotlin JPA plugin is also enabled. It generates no-arg
constructors for any class annotated with @Entity, @MappedSuperclass or @Embeddable.
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "3.2.2"
id("io.spring.dependency-management") version "1.1.4"
kotlin("jvm") version "1.9.22"
kotlin("plugin.spring") version "1.9.22"
kotlin("plugin.jpa") version "1.9.22"
}
Compiler options
One of Kotlin’s key features is null-safety - which cleanly deals with null values at compile time rather than bumping into the
famous NullPointerException at runtime. This makes applications safer through nullability declarations and expressing "value or
no value" semantics without paying the cost of wrappers like Optional.
Note that Kotlin allows using functional constructs with nullable values; check out this comprehensive guide to Kotlin null-
safety.
Although Java does not allow one to express null-safety in its type-system, Spring Framework provides null safety of the whole
Spring Framework API via tooling-friendly annotations declared in the
org.springframework.lang package. By default, types from Java APIs used in Kotlin are recognized as platform types for which
null-checks are relaxed. Kotlin support for JSR 305 annotations + Spring nullability annotations provide null-safety for the
whole Spring Framework API to Kotlin developers, with the advantage of dealing with null related issues at compile time.
This feature can be enabled by adding the -Xjsr305 compiler flag with the strict options.
build.gradle.kts
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs += "-Xjsr305=strict"
}
}
Dependencies
2 Kotlin specific libraries are required (the standard library is added automatically with Gradle) for such Spring Boot web
application and configured by default:
• jackson-module-kotlin adds support for serialization/deserialization of Kotlin classes and data classes 2 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
(single constructor classes can be used automatically, and those with secondary constructors or static factories are also
supported)
build.gradle.kts
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-mustache")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
runtimeOnly("com.h2database:h2")
runtimeOnly("org.springframework.boot:spring-boot-devtools")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
Recent versions of H2 require special configuration to properly escape reserved keywords like user.
src/main/resources/application.properties
spring.jpa.properties.hibernate.globally_quoted_identifiers=true
spring.jpa.properties.hibernate.globally_quoted_identifiers_skip_column_definitions=true
Spring Boot Gradle plugin automatically uses the Kotlin version declared via the Kotlin Gradle plugin. You can now take a
In order to be able to use Kotlin non-nullable properties with JPA, Kotlin JPA plugin is also enabled. It generates no-arg
constructors for any class annotated with @Entity, @MappedSuperclass or @Embeddable.
pom.xml
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<compilerPlugins>
<plugin>jpa</plugin>
<plugin>spring</plugin>
</compilerPlugins>
<args>
<arg>-Xjsr305=strict</arg>
</args>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-noarg</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
3 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
One of Kotlin’s key features is null-safety - which cleanly deals with null values at compile time rather than bumping into the
famous NullPointerException at runtime. This makes applications safer through nullability declarations and expressing "value or
no value" semantics without paying the cost of wrappers like Optional.
Note that Kotlin allows using functional constructs with nullable values; check out this comprehensive guide to Kotlin null-
safety.
Although Java does not allow one to express null-safety in its type-system, Spring Framework provides null safety of the whole
Spring Framework API via tooling-friendly annotations declared in the
org.springframework.lang package. By default, types from Java APIs used in Kotlin are recognized as platform types for which
null-checks are relaxed. Kotlin support for JSR 305 annotations + Spring nullability annotations provide null-safety for the
whole Spring Framework API to Kotlin developers, with the advantage of dealing with null related issues at compile time.
This feature can be enabled by adding the -Xjsr305 compiler flag with the strict options.
Notice also that Kotlin compiler is configured to generate Java 8 bytecode (Java 6 by default).
Dependencies
3 Kotlin specific libraries are required for such Spring Boot web application and configured by default: • kotlin-stdlib is the
• jackson-module-kotlin adds support for serialization/deserialization of Kotlin classes and data classes (single constructor
classes can be used automatically, and those with secondary constructors or static factories are also supported)
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mustache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
4 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
package com.example.blog
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class BlogApplication
Compared to Java, you can notice the lack of semicolons, the lack of brackets on empty class (you can add some if you need to
declare beans via @Bean annotation) and the use of runApplication top level function. runApplication<BlogApplication>(*args) is
Kotlin idiomatic alternative to
SpringApplication.run(BlogApplication::class.java, *args) and can be used to customize the application with following syntax.
src/main/kotlin/com/example/blog/BlogApplication.kt
package com.example.blog
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.ui.set
import org.springframework.web.bind.annotation.GetMapping
@Controller
class HtmlController {
@GetMapping("/")
fun blog(model: Model): String {
model["title"] = "Blog"
return "blog"
}
Notice that we are using here a Kotlin extension that allows to add Kotlin functions or operators to existing Spring types. Here
we import the org.springframework.ui.set extension function in order to be able to write model["title"] = "Blog" instead of
model.addAttribute("title", "Blog") . The Spring Framework KDoc API lists all the Kotlin extensions provided to enrich the Java
API.
5 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
<html>
<head>
<title>{{title}}</title>
</head>
<body>
src/main/resources/templates/footer.mustache
src/main/resources/templates/blog.mustache
{{> header}}
<h1>{{title}}</h1>
{{> footer}}
Start the web application by running the main function of BlogApplication.kt, and go to http://
localhost:8080/, you should see a sober web page with a "Blog" headline.
• We use real sentences between backticks instead of camel-case to provide expressive test function names
• JUnit 5 allows to inject constructor and method parameters, which is a good fit with Kotlin read-only and non-nullable
properties
• This code leverages getForObject and getForEntity Kotlin extensions (you need to import them)
src/test/kotlin/com/example/blog/IntegrationTests.kt
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTests(@Autowired val restTemplate: TestRestTemplate) {
@Test
fun `Assert blog page title, content and status code`() {
val entity = restTemplate.getForEntity<String>("/")
assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)
assertThat(entity.body).contains("<h1>Blog</h1>")
}
But Junit 5 allows you to change this default behavior and instantiate test classes one time per class. This can be done in various
ways, here we will use a property file to change the default behavior for the whole project:
src/test/resources/junit-platform.properties
junit.jupiter.testinstance.lifecycle.default = per_class
6 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
With this configuration, we can now use @BeforeAll and @AfterAll annotations on regular methods like shown in updated
version of IntegrationTests above.
src/test/kotlin/com/example/blog/IntegrationTests.kt
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTests(@Autowired val restTemplate: TestRestTemplate) {
@BeforeAll
fun setup() {
println(">> Setup")
}
@Test
fun `Assert blog page title, content and status code`() {
println(">> Assert blog page title, content and status code")
val entity = restTemplate.getForEntity<String>("/")
assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)
assertThat(entity.body).contains("<h1>Blog</h1>")
}
@Test
fun `Assert article page title, content and status code`() {
println(">> TODO")
}
@AfterAll
fun teardown() {
println(">> Tear down")
}
7 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
In order to make lazy fetching working as expected, entities should be open as described in KT-28525. We are going to use the
Kotlin allopen plugin for that purpose.
With Gradle:
build.gradle.kts
plugins {
...
kotlin("plugin.allopen") version "1.9.22"
}
allOpen {
annotation("jakarta.persistence.Entity")
annotation("jakarta.persistence.Embeddable")
annotation("jakarta.persistence.MappedSuperclass")
}
Or with Maven:
pom.xml
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<configuration>
...
<compilerPlugins>
...
<plugin>all-open</plugin>
</compilerPlugins>
<pluginOptions>
<option>all-open:annotation=jakarta.persistence.Entity</option>
<option>all-open:annotation=jakarta.persistence.Embeddable</option>
<option>all-open:annotation=jakarta.persistence.MappedSuperclass</option>
</pluginOptions>
</configuration>
</plugin>
Then we create our model by using Kotlin primary constructor concise syntax which allows to declare at the same time the
properties and the constructor parameters.
src/main/kotlin/com/example/blog/Entities.kt
@Entity
class Article(
var title: String,
var headline: String,
var content: String,
@ManyToOne var author: User,
var slug: String = title.toSlug(),
var addedAt: LocalDateTime = LocalDateTime.now(),
@Id @GeneratedValue var id: Long? = null)
@Entity
class User(
var login: String,
var firstname: String,
var lastname: String,
var description: String? = null,
@Id @GeneratedValue var id: Long? = null)
Notice that we are using here our String.toSlug() extension to provide a default argument to the slug parameter of Article
constructor. Optional parameters with default values are defined at the last position in order to make it possible to omit them
when using positional arguments (Kotlin also supports named arguments). Notice that in Kotlin it is not unusual to group
concise class declarations in the same file.
Here we don’t use data classes with val properties because JPA is not designed to work with immutable classes or the methods
generated automatically by data classes. If you are using other Spring Data flavor, most of them are designed to support such
constructs so you should use classes like data class User(val login: String, …) when using Spring Data MongoDB, Spring Data
JDBC, etc.
8 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
While Spring Data JPA makes it possible to use natural IDs (it could have been the login property in User class) via Persistable,
it is not a good fit with Kotlin due to KT-6653, that’s why it is recommended to always use entities with generated IDs in Kotlin.
And we write JPA tests to check whether basic use cases work as expected.
src/test/kotlin/com/example/blog/RepositoriesTests.kt
@DataJpaTest
class RepositoriesTests @Autowired constructor(
val entityManager: TestEntityManager,
val userRepository: UserRepository,
val articleRepository: ArticleRepository) {
@Test
fun `When findByIdOrNull then return Article`() {
val johnDoe = User("johnDoe", "John", "Doe")
entityManager.persist(johnDoe)
val article = Article("Lorem", "Lorem", "dolor sit amet", johnDoe)
entityManager.persist(article)
entityManager.flush()
val found = articleRepository.findByIdOrNull(article.id!!)
assertThat(found).isEqualTo(article)
}
@Test
fun `When findByLogin then return User`() {
val johnDoe = User("johnDoe", "John", "Doe")
entityManager.persist(johnDoe)
entityManager.flush()
val user = userRepository.findByLogin(johnDoe.login)
assertThat(user).isEqualTo(johnDoe)
}
}
We use here the CrudRepository.findByIdOrNull Kotlin extension provided by default with Spring Data, which is a nullable variant
of the Optional based CrudRepository.findById. Read the great Null is your friend, not a mistake blog post for more details.
{{> header}}
<h1>{{title}}</h1>
<div class="articles">
{{#articles}}
<section>
<header class="article-header">
<h2 class="article-title"><a href="/article/{{slug}}">{{title}}</a></h2>
<div class="article-meta">By <strong>{{author.firstname}}</strong>, on <strong>{{addedAt}}</strong></ </header>
<div class="article-description">
9 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
{{headline}}
</div>
</section>
{{/articles}}
</div>
{{> footer}}
{{> header}}
<section class="article">
<header class="article-header">
<h1 class="article-title">{{article.title}}</h1>
<p class="article-meta">By <strong>{{article.author.firstname}}</strong>, on <strong>{{article.addedAt}} </header>
<div class="article-description">
{{article.headline}}
{{article.content}}
</div>
</section>
{{> footer}}
We update the HtmlController in order to render blog and article pages with the formatted date.
ArticleRepository and MarkdownConverter constructor parameters will be automatically autowired since HtmlController has a
single constructor (implicit @Autowired).
src/main/kotlin/com/example/blog/HtmlController.kt
@Controller
class HtmlController(private val repository: ArticleRepository) {
@GetMapping("/")
fun blog(model: Model): String {
model["title"] = "Blog"
model["articles"] = repository.findAllByOrderByAddedAtDesc().map { it.render() }
return "blog"
}
@GetMapping("/article/{slug}")
fun article(@PathVariable slug: String, model: Model): String {
val article = repository
.findBySlug(slug)
?.render()
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "This article does not exist")
model["title"] = article.title
model["article"] = article
return "article"
}
10 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
@Configuration
class BlogConfiguration {
@Bean
fun databaseInitializer(userRepository: UserRepository,
articleRepository: ArticleRepository) = ApplicationRunner {
Notice the usage of named parameters to make the code more readable.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTests(@Autowired val restTemplate: TestRestTemplate) {
@BeforeAll
fun setup() {
println(">> Setup")
}
@Test
fun `Assert blog page title, content and status code`() {
println(">> Assert blog page title, content and status code")
val entity = restTemplate.getForEntity<String>("/")
assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)
assertThat(entity.body).contains("<h1>Blog</h1>", "Lorem")
}
@Test
fun `Assert article page title, content and status code`() {
println(">> Assert article page title, content and status code")
val title = "Lorem"
val entity = restTemplate.getForEntity<String>("/article/${title.toSlug()}")
assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)
assertThat(entity.body).contains(title, "Lorem", "dolor sit amet")
}
@AfterAll
fun teardown() {
println(">> Tear down")
}
Start (or restart) the web application, and go to http://localhost:8080/, you should see the list of articles with clickable links to
see a specific article.
11 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
src/main/kotlin/com/example/blog/HttpControllers.kt
@RestController
@RequestMapping("/api/article")
class ArticleController(private val repository: ArticleRepository) {
@GetMapping("/")
fun findAll() = repository.findAllByOrderByAddedAtDesc()
@GetMapping("/{slug}")
fun findOne(@PathVariable slug: String) =
repository.findBySlug(slug) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "This article does not exis}
@RestController
@RequestMapping("/api/user")
class UserController(private val repository: UserRepository) {
@GetMapping("/")
fun findAll() = repository.findAll()
@GetMapping("/{login}")
fun findOne(@PathVariable login: String) =
repository.findByLogin(login) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "This user does not exist }
For tests, instead of integration tests, we are going to leverage @WebMvcTest and Mockk which is similar to Mockito but better
suited for Kotlin.
Since @MockBean and @SpyBean annotations are specific to Mockito, we are going to leverage SpringMockK which provides similar
@MockkBean and @SpykBean annotations for Mockk.
With Gradle:
build.gradle.kts
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(module = "mockito-core")
}
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testImplementation("com.ninja-squad:springmockk:4.0.2")
Or with Maven:
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.ninja-squad</groupId>
<artifactId>springmockk</artifactId>
<version>4.0.2</version>
<scope>test</scope>
</dependency>
src/test/kotlin/com/example/blog/HttpControllersTests.kt
@WebMvcTest
class HttpControllersTests(@Autowired val mockMvc: MockMvc) {
@MockkBean
lateinit var userRepository: UserRepository
12 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
@MockkBean
lateinit var articleRepository: ArticleRepository
@Test
fun `List articles`() {
val johnDoe = User("johnDoe", "John", "Doe")
val lorem5Article = Article("Lorem", "Lorem", "dolor sit amet", johnDoe)
val ipsumArticle = Article("Ipsum", "Ipsum", "dolor sit amet", johnDoe)
every { articleRepository.findAllByOrderByAddedAtDesc() } returns listOf(lorem5Article, ipsumArticle)
mockMvc.perform(get("/api/article/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("\$.[0].author.login").value(johnDoe.login))
.andExpect(jsonPath("\$.[0].slug").value(lorem5Article.slug))
.andExpect(jsonPath("\$.[1].author.login").value(johnDoe.login))
.andExpect(jsonPath("\$.[1].slug").value(ipsumArticle.slug))
}
@Test
fun `List users`() {
val johnDoe = User("johnDoe", "John", "Doe")
val janeDoe = User("janeDoe", "Jane", "Doe")
every { userRepository.findAll() } returns listOf(johnDoe, janeDoe)
mockMvc.perform(get("/api/user/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("\$.[0].login").value(johnDoe.login))
.andExpect(jsonPath("\$.[1].login").value(janeDoe.login))
}
}
Configuration properties
In Kotlin, the recommended way to manage your application properties is to use read-only properties.
src/main/kotlin/com/example/blog/BlogProperties.kt
@ConfigurationProperties("blog")
data class BlogProperties(var title: String, val banner: Banner) {
data class Banner(val title: String? = null, val content: String)
}
@SpringBootApplication
@EnableConfigurationProperties(BlogProperties::class)
class BlogApplication {
// ...
}
To generate your own metadata in order to get these custom properties recognized by your IDE, kapt should be configured with
the spring-boot-configuration-processor dependency as following.
build.gradle.kts
plugins {
...
kotlin("kapt") version "1.9.22"
}
dependencies {
...
kapt("org.springframework.boot:spring-boot-configuration-processor")
}
Note that some features (such as detecting the default value or deprecated items) are not working due to limitations in the model
kapt provides. Also annotation processing is not yet supported with Maven due to
13 of 14 06-06-2024, 13:07
Getting Started | Building web applications with Spring Boot and Kotlin https://spring.io/guides/tutorials/spring-boot-kotlin
In IntelliJ IDEA:
• Make sure Spring Boot plugin in enabled in menu File | Settings | Plugins | Spring Boot
• Enable annotation processing via menu File | Settings | Build, Execution, Deployment | Compiler | Annotation Processors |
Enable annotation processing
• Since Kapt is not yet integrated in IDEA, you need to run manually the command ./gradlew kaptKotlin to generate the
metadata
Your custom properties should now be recognized when editing application.properties (autocomplete, validation, etc.).
src/main/resources/application.properties
blog.title=Blog
blog.banner.title=Warning
blog.banner.content=The blog will be down tomorrow.
{{> header}}
<div class="articles">
{{#banner.title}}
<section>
<header class="banner">
<h2 class="banner-title">{{banner.title}}</h2>
</header>
<div class="banner-content">
{{banner.content}}
</div>
</section>
{{/banner.title}}
...
</div>
{{> footer}}
src/main/kotlin/com/example/blog/HtmlController.kt
@Controller
class HtmlController(private val repository: ArticleRepository,
private val properties: BlogProperties) {
@GetMapping("/")
fun blog(model: Model): String {
model["title"] = properties.title
model["banner"] = properties.banner
model["articles"] = repository.findAllByOrderByAddedAtDesc().map { it.render() }
return "blog"
}
// ...
Restart the web application, refresh http://localhost:8080/, you should see the banner on the blog homepage.
Conclusion
We have now finished to build this sample Kotlin blog application. The source code is available on Github. You can also have a
look to Spring Framework and Spring Boot reference documentation if you need more details on specific features.
14 of 14 06-06-2024, 13:07