Skip to content

Java Spring – Spring Components

Open all questions about Spring

In this article:

Database configuration:

# Database connection URL
spring.datasource.url=jdbc:h2:mem:testdb
# Database driver
spring.datasource.driverClassName=org.h2.Driver
# Database username
spring.datasource.username=sa
# Database password
spring.datasource.password=password
# Database platform (using Hibernate)
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# Automatic database schema creation and update
spring.jpa.hibernate.ddl-auto=update

H2 console configuration:

# Enable H2 console
spring.h2.console.enabled=true
# Path to H2 console
spring.h2.console.path=/h2-console

Server configuration:

# Port on which the server is launched
server.port=8080
# Default encoding
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true

Security Configuration:

# Path to the login page
spring.security.user.name=user
# Password for login page
spring.security.user.password=secret
# User role
spring.security.user.roles=USER

Caching configuration:

# Enable caching
spring.cache.type=simple

Logging configuration:

# Logging level for the application
logging.level.root=INFO
logging.level.org.springframework.web=DEBUG
# Path to file lairs
logging.file.name=logs/spring-boot-application.log
# Log output format
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} - %msg%n

Internationalization configuration:

# Locale by default
spring.mvc.locale=ru_RU
# Enable automatic locale detection
spring.mvc.locale-resolver=accept-header

Email sending configuration:

# SMTP server
spring.mail.host=smtp.example.com
# SMTP port
spring.mail.port=587
# Username for SMTP
[email protected]
# Password for SMTP
spring.mail.password=password
# Protocol
spring.mail.protocol=smtp
# Path to email templates
spring.mail.templates.path=classpath:/templates/

Application settings configuration:

# Example of user parameter
myapp.custom-property=value

Add dependencies for Spring WebSocket to your build.gradle.kts (if you are using the Kotlin DSL for Gradle):

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-websocket")
    implementation("org.springframework.boot:spring-boot-starter-web")
}

WebSocket Configuration

import org.springframework.context.annotation.Configuration
import org.springframework.web.socket.config.annotation.EnableWebSocket
import org.springframework.web.socket.config.annotation.WebSocketConfigurer
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry

@Configuration
@EnableWebSocket
class WebSocketConfig(private val webSocketHandler: MyWebSocketHandler) : WebSocketConfigurer {
    override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
        registry.addHandler(webSocketHandler, "/ws").setAllowedOrigins("*")
    }
}

Implementing a WebSocket Handler

import org.springframework.stereotype.Component
import org.springframework.web.socket.TextMessage
import org.springframework.web.socket.WebSocketSession
import org.springframework.web.socket.handler.TextWebSocketHandler

@Component
class MyWebSocketHandler : TextWebSocketHandler() {
    override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
        val payload = message.payload
        println("Received: $payload")
        val response = TextMessage("Server received: $payload")
        session.sendMessage(response)
    }
}

Adding dependencies

dependencies {
    implementation("com.graphql-java-kickstart:graphql-spring-boot-starter:11.1.0")
    implementation("com.graphql-java-kickstart:graphiql-spring-boot-starter:11.1.0")
    implementation("com.graphql-java-kickstart:altair-spring-boot-starter:11.1.0")
    implementation("com.graphql-java-kickstart:voyager-spring-boot-starter:11.1.0")
    implementation("com.graphql-java-kickstart:graphql-java-tools:11.1.0")
}

GraphQL Schema Definition:
Create a schema.graphqls file in the src/main/resources folder.

type Query {
    getUser(id: ID!): User
    getAllUsers: [User]
}

type Mutation {
    createUser(input: CreateUserInput): User
}

type User {
    id: ID!
    name: String!
    email: String!
}

input CreateUserInput {
    name: String!
    email: String!
}
data class User(
    val id: Long,
    val name: String,
    val email: String
)
import org.springframework.stereotype.Repository

@Repository
class UserRepository {
    private val users = mutableListOf<User>()
    private var idCounter = 1L
    fun getUserById(id: Long): User? {
        return users.find { it.id == id }
    }
    fun getAllUsers(): List<User> {
        return users
    }
    fun createUser(name: String, email: String): User {
        val user = User(id = idCounter++, name = name, email = email)
        users.add(user)
        return user
    }
}

GraphQL Resolvers

import com.coxautodev.graphql.tools.GraphQLQueryResolver
import org.springframework.stereotype.Component

@Component
class UserQueryResolver(private val userRepository: UserRepository) : GraphQLQueryResolver {
    fun getUser(id: Long): User? {
        return userRepository.getUserById(id)
    }
    fun getAllUsers(): List<User> {
        return userRepository.getAllUsers()
    }
}
import com.coxautodev.graphql.tools.GraphQLMutationResolver
import org.springframework.stereotype.Component

data class CreateUserInput(val name: String, val email: String)

@Component
class UserMutationResolver(private val userRepository: UserRepository) : GraphQLMutationResolver {
    fun createUser(input: CreateUserInput): User {
        return userRepository.createUser(input.name, input.email)
    }
}

Examples of queries and mutations

Request to get user by ID

query {
    getUser(id: 1) {
        id
        name
        email
    }
}

Request to get all users

query {
    getAllUsers {
        id
        name
        email
    }
}

Mutation to create a new user

mutation {
    createUser(input: { name: "John Doe", email: "[email protected]" }) {
        id
        name
        email
    }
}

Setting up OAuth 2.0 in Spring Boot allows your application to securely interact with third-party services such as Google, Facebook, and other OAuth providers. In this example, I will show how to set up a Spring Boot application to authenticate users via OAuth 2.0 using the Google provider.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
    implementation 'org.jetbrains.kotlin:kotlin-reflect'
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
spring.security.oauth2.client.registration.google.client-id=YOUR_CLIENT_ID
spring.security.oauth2.client.registration.google.client-secret=YOUR_CLIENT_SECRET
spring.security.oauth2.client.registration.google.scope=profile,email
spring.security.oauth2.client.registration.google.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.google.client-name=Google
spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/auth
spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token
spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo
spring.security.oauth2.client.provider.google.user-name-attribute=sub
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping

@Controller
class HomeController {
    @GetMapping("/")
    fun home(model: Model, @AuthenticationPrincipal principal: OidcUser?): String {
        if (principal != null) {
            model.addAttribute("name", principal.name)
            model.addAttribute("email", principal.email)
        }
        return "home"
    }
    @GetMapping("/login")
    fun login(): String {
        return "login"
    }
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeRequests { requests ->
                requests.antMatchers("/", "/login").permitAll()
                    .anyRequest().authenticated()
            }.oauth2Login { oauth2Login ->
                oauth2Login.loginPage("/login")
                    .defaultSuccessURL("/")
            }
        return http.build()
    }
}

RestTemplate, WebClient, FeignClient, Apache HttpClient, OkHttp, Retrofit, HTTP4K, Ktor can be used for this

RestTemplate:
Nature: Synchronous.
Usage: A simple, blocking HTTP client.
Status: In support mode; it is recommended to switch to WebClient.
Typical use: Legacy projects and simple HTTP calls.
Advantages: Ease of use, familiar to many developers.

WebClient:
Nature: Asynchronous and non-blocking.
Usage: Part of Spring WebFlux, supports reactive programming.
Status: Modern client for HTTP requests.
Typical use: Highly loaded systems and applications that require high performance.
Advantages: Support for asynchronous and synchronous requests, data streams.

FeignClient:
Nature: Synchronous and asynchronous.
Usage: A high-level client for interacting with external APIs.
Status: Integrates with Spring Cloud, used for microservices.
Typical use: Microservice architectures.
Advantages: Automatic client creation, high abstraction.

Apache HttpClient:
Nature: Synchronous and asynchronous.
Usage: Low-level HTTP client with many configurations.
Status: Widely used, powerful and flexible.
Typical use: Cases where low-level control over HTTP requests is required.
Advantages: Flexibility and powerful customization options.

OkHttp:
Nature: Synchronous and asynchronous.
Usage: Modern HTTP client with simple API.
Status: Often used in combination with Retrofit.
Typical use: Android development and JVM-based applications.
Advantages: Simple and clean API.

Retrofit:
Nature: Synchronous and asynchronous.
Usage: High-level client for working with REST API, integrates with OkHttp.
Status: A popular choice for Android and microservices architectures.
Typical use: Android and microservices.
Advantages: Automatic creation of clients, support for various converters.

HTTP4K:
Nature: Asynchronous.
Usage: A functional library for creating HTTP clients and servers.
Status: A lightweight and flexible library.
Typical use: Projects requiring functional programming.
Advantages: Functional approach, simplicity and flexibility.

Who:
Nature: Asynchronous.
Usage: A framework from JetBrains for creating server and client applications in Kotlin.
Status: A modular and easily customizable library.
Typical use: High performance asynchronous applications.
Advantages: Excellent integration with Kotlin, high performance.

Comparative analysis of use:

RestTemplate:
Still used in legacy projects, but its usage is declining.

WebClient:
An increasingly popular choice for new projects, especially those that require asynchrony and reactivity.

FeignClient:
Often used in microservice architectures integrated with Spring Cloud.

Apache HttpClient and OkHttp:
They are used for low-level control and in specific tasks.

Retrofit:
Popular in Android development.

HTTP4K and Ktor:
Less common, but popular among Kotlin developers and those who prefer functional and modular approaches.

RestTemplate:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
}
rest:
  url: https://api.example.com/data
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.client.RestTemplate

@Configuration
class AppConfig {
    @Bean
    fun restTemplate(): RestTemplate {
        return RestTemplate()
    }
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

@Service
class ApiService(@Autowired private val restTemplate: RestTemplate) {
    fun getDataFromExternalApi(url: String): String? {
        return restTemplate.getForObject(url, String::class.java)
    }
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpEntity
import org.springframework.http.HttpMethod
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate

data class RequestObject(val name: String, val value: String)
data class ResponseObject(val id: Long, val status: String)

@Service
class ApiService(@Autowired private val restTemplate: RestTemplate) {
    fun sendAndReceiveObject(url: String, requestObject: RequestObject): ResponseObject? {
        val requestEntity = HttpEntity(requestObject)
        val responseEntity: ResponseEntity<ResponseObject> = restTemplate.exchange(
            url,
            HttpMethod.POST,
            requestEntity,
            ResponseObject::class.java
        )
        return responseEntity.body
    }
}

WebClient:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
}
webclient:
  base-url: https://api.example.com
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.function.client.WebClient

@Configuration
class WebClientConfig {
    @Bean
    fun webClient(): WebClient {
        return WebClient.builder().build()
    }
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient

@Service
class ApiService(@Autowired private val webClient: WebClient) {
    fun getDataFromExternalApi(url: String): String? {
        return webClient.get()
            .uri(url)
            .retrieve()
            .bodyToMono(String::class.java)
            .block()
    }
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono

data class RequestObject(val name: String, val value: String)
data class ResponseObject(val id: Long, val status: String)

@Service
class ApiService(@Autowired private val webClient: WebClient) {
    fun sendAndReceiveObject(url: String, requestObject: RequestObject): ResponseObject? {
        return webClient.post()
            .uri(url)
            .body(Mono.just(requestObject), RequestObject::class.java)
            .retrieve()
            .bodyToMono(ResponseObject::class.java)
            .block()
    }
}

FeignClient:

dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
feign:
  client:
    config:
      externalApiClient:
        url: https://api.example.com
import org.springframework.cloud.openfeign.EnableFeignClients
import org.springframework.context.annotation.Configuration

@Configuration
@EnableFeignClients
class FeignConfig
import org.springframework.cloud.openfeign.FeignClient
import org.springframework.web.bind.annotation.GetMapping

@FeignClient(name = "externalApiClient", url = "https://api.external.com")
interface ExternalApiClient {
    @GetMapping("/data")
    fun getData(): String
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

@Service
class ApiService(@Autowired private val externalApiClient: ExternalApiClient) {
    fun getDataFromExternalApi(): String {
        return externalApiClient.getData()
    }
}

Apache HttpClient:

dependencies {
    implementation 'org.apache.httpcomponents:httpclient:4.5.13'
}
import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
import org.apache.http.util.EntityUtils

fun sendHttpRequest(url: String): String? {
    val httpClient: CloseableHttpClient = HttpClients.createDefault()
    val httpGet = HttpGet(url)
    httpClient.execute(httpGet).use { response ->
        return EntityUtils.toString(response.entity)
    }
}

OkHttp:

dependencies {
    implementation 'com.squareup.okhttp3:okhttp:4.9.0'
}
import okhttp3.OkHttpClient
import okhttp3.Request

fun sendOkHttpRequest(url: String): String? {
    val client = OkHttpClient()
    val request = Request.Builder()
        .url(url)
        .build()
    client.newCall(request).execute().use { response ->
        return response.body?.string()
    }
}

Retrofit:

dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
}
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST

data class RequestObject(val name: String, val value: String)
data class ResponseObject(val id: Long, val status: String)

interface ApiService {
    @POST("data")
    suspend fun sendData(@Body requestObject: RequestObject): ResponseObject
    @GET("data")
    suspend fun getData(): ResponseObject
}

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

val apiService = retrofit.create(ApiService::class.java)

Spring WebClient with Reactor:

import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono

val webClient = WebClient.create("https://api.example.com")

fun sendAndReceiveReactiveObject(requestObject: RequestObject): Mono<ResponseObject> {
    return webClient.post()
        .uri("/data")
        .body(Mono.just(requestObject), RequestObject::class.java)
        .retrieve()
        .bodyToMono(ResponseObject::class.java)
}

HTTP4K:

dependencies {
    implementation "org.http4k:http4k-core:4.9.9.0"
    implementation "org.http4k:http4k-client-okhttp:4.9.9.0"
}
import org.http4k.client.OkHttp
import org.http4k.core.Method
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.format.Gson.auto
import org.http4k.lens.Body

data class RequestObject(val name: String, val value: String)
data class ResponseObject(val id: Long, val status: String)

val client = OkHttp()

fun sendHttp4kRequest(url: String, requestObject: RequestObject): ResponseObject? {
    val requestLens = Body.auto<RequestObject>().toLens()
    val responseLens = Body.auto<ResponseObject>().toLens()
    val request = Request(Method.POST, url)
        .with(requestLens of requestObject)
    val response: Response = client(request)
    return responseLens.extract(response)
}

Who:

dependencies {
    implementation "io.ktor:ktor-client-core:1.5.2"
    implementation "io.ktor:ktor-client-cio:1.5.2"
    implementation "io.ktor:ktor-client-json:1.5.2"
    implementation "io.ktor:ktor-client-serialization:1.5.2"
}
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import kotlinx.serialization.Serializable

@Serializable
data class RequestObject(val name: String, val value: String)

@Serializable
data class ResponseObject(val id: Long, val status: String)

val client = HttpClient(CIO) {
    install(JsonFeature) {
        serializer = KotlinxSerializer()
    }
}

suspend fun sendKtorRequest(url: String, requestObject: RequestObject): ResponseObject {
    return client.post(url) {
        body = requestObject
    }
}

In Spring, you can set up data encryption using various libraries and approaches. One popular way is to use Spring Security Crypto to encrypt and decrypt data. In this example, we will look at how to set up password encryption using BCrypt, as well as how to encrypt data using Jasypt.

Encrypting passwords with BCrypt:
BCrypt is a hash function specifically designed for password hashing. It provides strong protection against attacks such as dictionary attacks and brute-force attacks.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-security")
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun passwordEncoder(): PasswordEncoder {
        return BCryptPasswordEncoder()
    }
    @Bean
    fun userDetailsService(passwordEncoder: PasswordEncoder): UserDetailsService {
        val user = User.withUsername("user")
            .password(passwordEncoder.encode("password"))
            .roles("USER")
            .build()
        return InMemoryUserDetailsManager(user)
    }
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .httpBasic()
        return http.build()
    }
}

Encrypting data with Jasypt:
Jasypt (Java Simplified Encryption) is a library that provides simple ways to encrypt and decrypt data.

dependencies {
    implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4")
}
jasypt.encryptor.password=yourEncryptionPassword
import org.jasypt.encryption.StringEncryptor
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor
import org.jasypt.spring31.properties.EncryptablePropertyPlaceholderConfigurer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.env.Environment
import java.util.*

@Configuration
class JasyptConfig {
    @Bean(name = ["jasyptStringEncryptor"])
    fun stringEncryptor(): StringEncryptor {
        val encryptor = StandardPBEStringEncryptor()
        encryptor.setPassword("yourEncryptionPassword")
        return encryptor
    }
    @Bean
    fun propertyPlaceholderConfigurer(environment: Environment): EncryptablePropertyPlaceholderConfigurer {
        val configurer = EncryptablePropertyPlaceholderConfigurer(stringEncryptor())
        configurer.setLocation(environment)
        return configurer
    }
}
import org.jasypt.util.text.AES256TextEncryptor
import org.springframework.stereotype.Service

@Service
class DataService {
    private val textEncryptor = AES256TextEncryptor().apply {
        setPassword("yourEncryptionPassword")
    }
    fun encryptData(data: String): String {
        return textEncryptor.encrypt(data)
    }
    fun decryptData(encryptedData: String): String {
        return textEncryptor.decrypt(encryptedData)
    }
}
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
class DataController(private val dataService: DataService) {
    @GetMapping("/encrypt")
    fun encrypt(@RequestParam data: String): String {
        return dataService.encryptData(data)
    }
    @GetMapping("/decrypt")
    fun decrypt(@RequestParam encryptedData: String): String {
        return dataService.decryptData(encryptedData)
    }
}

Using SSL/TLS certificates to provide secure communication in a Spring Boot application involves setting up HTTPS. This involves creating or obtaining an SSL certificate, setting up the server, and updating the Spring Boot configuration. Below is a step-by-step example of setting up HTTPS using a self-signed certificate.

Generating a self-signed certificate:
To generate a self-signed certificate, you can use keytool, which is included with the JDK.

keytool -genkeypair -alias myalias -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650

myalias:
key alias.

keystore.p12:
Keystore file name.

validity 3650:
certificate validity period (in days).

When you run this command, you will be asked to enter various details such as the keystore password, your name, organization, etc. After this, the keystore.p12 file will be created.

Configuring Spring Boot to use SSL:
Add SSL settings to the application.properties file.

server.port=8443
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=your_password
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=myalias

Moving the keystore.p12 file to the resource folder

Move the keystore.p12 file to the src/main/resources folder of your project so that Spring Boot can find it.

Update security configuration (if required)

If your application uses Spring Security, you can configure it to support HTTPS.

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeRequests { requests ->
                requests.anyRequest().authenticated()
            }.formLogin { form ->
                form.loginPage("/login").permitAll()
            }.logout { logout ->
                logout.permitAll()
            }.requiresChannel { channel ->
                channel.anyRequest().requiresSecure()
            }
        return http.build()
    }
}

Spring Boot Actuator is a powerful tool in the Spring Boot ecosystem that provides out-of-the-box functionality for monitoring and managing running applications. Actuator provides a set of endpoints that can be used to retrieve information about the application, perform management operations, and monitor its health.

Key features of Spring Boot Actuator:

Status monitoring:
Provides information about the application state, such as performance metrics, database information, system data, etc.

Application management:
Allows you to perform administrative operations such as restarting the application, retrieving configuration information, etc.

Integration with monitoring tools:
Easily integrates with monitoring and alerting tools such as Prometheus, Grafana and others.

Enabling Spring Boot Actuator:
To get started with Actuator, you need to add a dependency to your project.
Example project using Gradle

plugins {
    id("org.springframework.boot") version "2.6.2"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.6.0"
    kotlin("plugin.spring") version "1.6.0"
}
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

Spring Boot Actuator Setup:
By default, some Actuator endpoints are enabled, but access to them is restricted. You can configure which endpoints are available, as well as their security level.
application.properties

# Include all Actuator endpoints
management.endpoints.web.exposure.include=*
# Set the path to Actuator endpoints
management.endpoints.web.base-path=/actuator

Actuator's main endpoints are:

/actuator/health:
Checking the application status.

/actuator/info:
General information about the application.

/actuator/metrics:
Application performance metrics.

/actuator/env:
Information about the current environment configuration.

/actuator/beans:
List of all Spring beans in the application context.

/actuator/threaddump:
Snapshot of current flows.

/actuator/loggers:
Logging levels and how to change them.

Examples of queries:
Checking the application status

curl http://localhost:8080/actuator/health

Getting information about the application

curl http://localhost:8080/actuator/info

Getting metrics

curl http://localhost:8080/actuator/metrics

Actuator Endpoint Protection:
Spring Security can be used to secure Actuator endpoints, allowing you to restrict access to administrative functions to authorized users only.

Example of security configuration:
In this example, access to Actuator endpoints is restricted to users with the ADMIN role.

import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter

@Configuration
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
    override fun configure(http: HttpSecurity) {
        http.authorizeRequests()
            .antMatchers("/actuator/**").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .httpBasic()
    }
}
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeHttpRequests { authz ->
                authz
                    .requestMatchers("/public/**").permitAll()
                    .anyRequest().authenticated()
            }.formLogin { form ->
                form
                    .loginPage("/login")
                    .permitAll()
            }.logout { logout ->
                logout.permitAll()
            }
        return http.build()
    }

    @Bean
    fun userDetailsService(): UserDetailsService {
        val user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build()

        val admin = User.withDefaultPasswordEncoder()
            .username("admin")
            .password("admin")
            .roles("ADMIN")
            .build()
        return InMemoryUserDetailsManager(user, admin)
    }
}
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping

@Controller
class HomeController {
    @GetMapping("/")
    fun home(model: Model): String {
        model.addAttribute("message", "Welcome to the Home Page!")
        return "home"
    }
    @GetMapping("/login")
    fun login(): String {
        return "login"
    }
}

src/main/resources/templates/home.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Home</title>
</head>
<body>
    <h1 th:text="${message}">Welcome to the Home Page!</h1>
    <a href="/logout">Logout</a>
</body>
</html>

src/main/resources/templates/login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form th:action="@{/login}" method="post">
        <div>
            <label>Username:</label>
            <input type="text" name="username"/>
        </div>
        <div>
            <label>Password:</label>
            <input type="password" name="password"/>
        </div>
        <div>
            <button type="submit">Login</button>
        </div>
    </form>
</body>
</html>

User:
To create a custom class that extends UserDetails, you need to create a class that implements the UserDetails interface. This class will be used to represent the user in the Spring security system.

import javax.persistence.*

@Entity
data class UserEntity(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    val username: String,
    val password: String,
    val enabled: Boolean = true,
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "users_roles",
        joinColumns = [JoinColumn(name = "user_id")],
        inverseJoinColumns = [JoinColumn(name = "role_id")]
    )
    val roles: Set<RoleEntity> = HashSet()
)
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id

@Entity
data class RoleEntity(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    val name: String
)
import org.springframework.data.jpa.repository.JpaRepository

interface UserRepository : JpaRepository<UserEntity, Long> {
    fun findByUsername(username: String): UserEntity?
}
import org.springframework.data.jpa.repository.JpaRepository

interface RoleRepository : JpaRepository<RoleEntity, Long> {
    fun findByName(name: String): RoleEntity?
}
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails

class CustomUserDetails(private val user: UserEntity) : UserDetails {
    override fun getAuthorities(): Collection<GrantedAuthority> {
        return user.roles.map { SimpleGrantedAuthority(it.name) }
    }
    override fun getPassword(): String {
        return user.password
    }
    override fun getUsername(): String {
        return user.username
    }
    override fun isAccountNonExpired(): Boolean {
        return true
    }
    override fun isAccountNonLocked(): Boolean {
        return true
    }
    override fun isCredentialsNonExpired(): Boolean {
        return true
    }
    override fun isEnabled(): Boolean {
        return user.enabled
    }
}
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service

@Service
class CustomUserDetailsService(private val userRepository: UserRepository) : UserDetailsService {
    override fun loadUserByUsername(username: String): UserDetails {
        val user = userRepository.findByUsername(username)
            ?: throw UsernameNotFoundException("User not found with username: $username")
        return CustomUserDetails(user)
    }
}
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
@Transactional
class UserService(
    private val userRepository: UserRepository,
    private val roleRepository: RoleRepository
) {
    fun createUser(username: String, password: String, roles: Set<String>): UserEntity {
        val roleEntities = roles.map { roleRepository.findByName(it) ?: RoleEntity(name = it) }.toSet()
        val user = UserEntity(username = username, password = password, roles = roleEntities)
        return userRepository.save(user)
    }
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig(
  private val customUserDetailsService: CustomUserDetailsService
) {

    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeRequests { authz ->
                authz.antMatchers("/public/**").permitAll()
                    .anyRequest().authenticated()
            }.formLogin { form ->
                form.loginPage("/login")
                    .permitAll()
            }.logout { logout ->
                logout.permitAll()
            }
        return http.build()
    }

    @Bean
    fun userDetailsService(): UserDetailsService {
        return customUserDetailsService
    }

    @Bean
    fun passwordEncoder(): PasswordEncoder {
        return BCryptPasswordEncoder()
    }
}

Using UserService to create a user

import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component

@Component
class DataInitializer(private val userService: UserService) : CommandLineRunner {
    override fun run(vararg args: String?) {
        userService.createUser("user", passwordEncoder().encode("password"), setOf("ROLE_USER"))
        userService.createUser("admin", passwordEncoder().encode("admin"), setOf("ROLE_ADMIN"))
    }
    fun passwordEncoder(): PasswordEncoder {
        return BCryptPasswordEncoder()
    }
}

In Spring Security, you can use the @PreAuthorize and @Secured annotations to restrict access to controller or service methods based on roles. Here are some examples of how to use them.

import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping

@Controller
class UserController {
    @PreAuthorize("hasRole('ROLE_USER')")
    @GetMapping("/user")
    fun userPage(model: Model): String {
        model.addAttribute("message", "Welcome, User!")
        return "user"
    }
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/admin")
    fun adminPage(model: Model): String {
        model.addAttribute("message", "Welcome, Admin!")
        return "admin"
    }
}
import org.springframework.security.access.annotation.Secured
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping

@Controller
class AdminController {
    @Secured("ROLE_USER")
    @GetMapping("/user-secured")
    fun userPageSecured(model: Model): String {
        model.addAttribute("message", "Welcome, User! (Secured)")
        return "user-secured"
    }
    @Secured("ROLE_ADMIN")
    @GetMapping("/admin-secured")
    fun adminPageSecured(model: Model): String {
        model.addAttribute("message", "Welcome, Admin! (Secured)")
        return "admin-secured"
    }
}

In-Memory Storage:
In-Memory user storage is the simplest way to create users. You've already seen the example above. Here's another example for clarity:

@Bean
fun userDetailsService(): UserDetailsService {
    val user = User.withDefaultPasswordEncoder()
        .username("user")
        .password("password")
        .roles("USER")
        .build()
    val admin = User.withDefaultPasswordEncoder()
        .username("admin")
        .password("admin")
        .roles("ADMIN")
        .build()
    return InMemoryUserDetailsManager(user, admin)
}

JDBC Storage:
To use JDBC storage, you will need to configure the database and use JdbcUserDetailsManager. Example:

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    runtimeOnly("com.h2database:h2") // Or another database driver
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.User
import org.springframework.security.provisioning.JdbcUserDetailsManager
import org.springframework.security.provisioning.UserDetailsManager
import org.springframework.security.web.SecurityFilterChain
import javax.sql.DataSource

@Configuration
@EnableWebSecurity
class SecurityConfig {
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeHttpRequests { authz ->
                authz.requestMatchers("/public/**").permitAll()
                    .anyRequest().authenticated()
            }.formLogin { form ->
                form.loginPage("/login")
                    .permitAll()
            } .logout { logout ->
                logout.permitAll()
            }
        return http.build()
    }
    @Bean
    fun userDetailsService(dataSource: DataSource): UserDetailsManager {
        val userDetailsManager = JdbcUserDetailsManager(dataSource)
        // Adding users if they are not in the database
        if (!userDetailsManager.userExists("user")) {
            val user = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build()
            userDetailsManager.createUser(user)
        }
        if (!userDetailsManager.userExists("admin")) {
            val admin = User.withDefaultPasswordEncoder()
                .username("admin")
                .password("admin")
                .roles("ADMIN")
                .build()
            userDetailsManager.createUser(admin)
        }
        return userDetailsManager
    }
}
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.h2.console.enabled=true

Scripts for creating tables:
Spring Security provides ready-made SQL scripts for creating user and role tables. Add them to src/main/resources/schema.sql:

CREATE TABLE users (
    username VARCHAR(50) NOT NULL PRIMARY KEY,
    password VARCHAR(500) NOT NULL,
    enabled BOOLEAN NOT NULL
);

CREATE TABLE authorities (
    username VARCHAR(50) NOT NULL,
    authority VARCHAR(50) NOT NULL,
    FOREIGN KEY (username) REFERENCES users (username)
);

CREATE UNIQUE INDEX ix_auth_username ON authorities (username, authority);

Custom UserDetailsService:
If you need a custom store, create your own implementation of UserDetailsService.

import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service

@Service
class CustomUserDetailsService : UserDetailsService {
    override fun loadUserByUsername(username: String): UserDetails {
        // Replace this code with your own storage (eg database query)
        if (username == "user") {
            return User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build()
        } else {
            throw UsernameNotFoundException("User not found")
        }
    }
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig(
  val customUserDetailsService: CustomUserDetailsService
) {

    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeHttpRequests { authz ->
                authz .requestMatchers("/public/**").permitAll()
                    .anyRequest().authenticated()
            }.formLogin { form ->
                form.loginPage("/login")
                    .permitAll()
            }.logout { logout ->
                logout.permitAll()
            }
        return http.build()
    }

    @Bean
    fun userDetailsService(): UserDetailsService {
        return customUserDetailsService
    }
}

Spring Integration is a module in the Spring ecosystem designed to create enterprise integration solutions. It provides support for Enterprise Integration Patterns (EIPs), which help developers design and implement the integration of various systems and components in an application. Spring Integration makes it easy to connect and coordinate interactions between different systems and components, ensuring scalability, reliability, and ease of support.

Key features of Spring Integration:

Support for Integration Templates (EIP):
Includes support for popular integration patterns such as message routing, message transformation, message filtering, and message aggregation.

Connection with various systems:
Provides connectivity to various data sources and external systems such as databases, message queues (JMS, RabbitMQ), web services (REST, SOAP), files and others.

Modular architecture:
Provides a modular architecture that allows you to easily add and configure integration components.

Extensibility:
Allows developers to easily extend functionality by adding their own integration components.

The main components of Spring Integration are:

Message:
The basic data structure that is passed between components. A message consists of a payload and headers.

Channel:
A transport mechanism through which messages are transmitted. Channels can be point-to-point or publish-subscribe.

Endpoint:
Components that send or receive messages. Endpoints include message transformers, routers, filters, and other message handlers.

Gateway:
Interfaces that provide integration with external systems and protocols. Gateways can be used to send and receive messages from/to web services, message queues, and other systems.

Example of using Spring Integration:
Let's look at an example where we use Spring Integration to read messages from a JMS queue and process them.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-integration")
    implementation("org.springframework.boot:spring-boot-starter-activemq")
    implementation("org.springframework.integration:spring-integration-jms")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.integration.annotation.ServiceActivator
import org.springframework.integration.channel.DirectChannel
import org.springframework.integration.config.EnableIntegration
import org.springframework.integration.core.MessageHandler
import org.springframework.integration.jms.dsl.Jms
import org.springframework.jms.annotation.EnableJms
import org.springframework.jms.core.JmsTemplate
import javax.jms.ConnectionFactory

@Configuration
@EnableIntegration
@EnableJms
class IntegrationConfig {
    @Bean
    fun inputChannel() = DirectChannel()
    @Bean
    fun jmsInboundAdapter(connectionFactory: ConnectionFactory) =
        Jms.inboundAdapter(connectionFactory)
            .destination("inputQueue")
            .channel(inputChannel())
            .get()
    @Bean
    @ServiceActivator(inputChannel = "inputChannel")
    fun messageHandler(): MessageHandler {
        return MessageHandler { message ->
            println("Received message: ${message.payload}")
        }
    }
}

Sending messages to a JMS queue

import org.springframework.jms.core.JmsTemplate
import org.springframework.stereotype.Component

@Component
class JmsProducer(private val jmsTemplate: JmsTemplate) {
    fun sendMessage(destination: String, message: String) {
        jmsTemplate.convertAndSend(destination, message)
    }
}
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import javax.annotation.PostConstruct

@SpringBootApplication
class DemoApplication(private val jmsProducer: JmsProducer) {
    @PostConstruct
    fun init() {
        jmsProducer.sendMessage("inputQueue", "Hello, Spring Integration!")
    }
}

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}

Spring Batch is a module in the Spring ecosystem designed to build scalable, reliable, and multi-threaded batch applications. It provides a powerful framework for processing large amounts of data in batch mode, including reading, processing, and writing data.

Key features of Spring Batch:

Reading, processing and writing data:
Supports various data sources for reading, such as databases, files, message queues, etc.
Allows you to perform complex data processing operations.
Supports writing data to various sources.

Modular architecture:
A batch job (Job) consists of steps (Step), each of which can be configured independently.
Each step includes three main phases: Read, Process, Write.

Transaction and Failure Management:
Supports transaction management, rollbacks, and retries to ensure data reliability and integrity.

Planning and monitoring:
Integration with various task schedulers (Quartz, Spring Scheduling, etc.).
Support for monitoring and auditing the execution of batch tasks.

Support for parallelism and multithreading:
Provides the ability to execute steps and tasks in multi-threaded mode to improve performance.

The main components of Spring Batch are:

Job:
A batch task consisting of one or more steps (Step). Specifies which steps should be performed and in what order.

Step:
A unit of execution within a job. Consists of data reading, processing, and writing steps. Each step can have its own logic and configuration.

ItemReader:
A component that is responsible for reading data from a source. Examples include FlatFileItemReader for reading data from a file and JdbcCursorItemReader for reading data from a database.

ItemProcessor:
A component that is responsible for data processing. Can perform any transformations or filtering of data.

ItemWriter:
A component that is responsible for writing data to a target source. Examples include FlatFileItemWriter for writing data to a file and JdbcBatchItemWriter for writing data to a database.

Example of using Spring Batch:
Let's look at an example of using Spring Batch to read data from a CSV file, process it, and write it to a database.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-batch")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    runtimeOnly("com.h2database:h2")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import org.springframework.batch.core.Job
import org.springframework.batch.core.Step
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory
import org.springframework.batch.core.launch.support.RunIdIncrementer
import org.springframework.batch.item.ItemProcessor
import org.springframework.batch.item.ItemReader
import org.springframework.batch.item.ItemWriter
import org.springframework.batch.item.file.FlatFileItemReader
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper
import org.springframework.batch.item.file.mapping.DefaultLineMapper
import org.springframework.batch.item.file.mapping.DelimitedLineTokenizer
import org.springframework.batch.item.file.transform.LineTokenizer
import org.springframework.batch.item.file.transform.Range
import org.springframework.batch.item.support.ListItemReader
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.ClassPathResource
import javax.sql.DataSource

@Configuration
@EnableBatchProcessing
class BatchConfig(
    private val jobBuilderFactory: JobBuilderFactory,
    private val stepBuilderFactory: StepBuilderFactory,
    private val dataSource: DataSource
) {
    @Bean
    fun personReader(): ItemReader<Person> {
        val reader = FlatFileItemReader<Person>()
        reader.setResource(ClassPathResource("people.csv"))
        reader.setLineMapper(DefaultLineMapper<Person>().apply {
            setLineTokenizer(DelimitedLineTokenizer().apply {
                setNames("firstName", "lastName")
            })
            setFieldSetMapper(BeanWrapperFieldSetMapper<Person>().apply {
                setTargetType(Person::class.java)
            })
        })
        return reader
    }
    @Bean
    fun personProcessor(): ItemProcessor<Person, Person> {
        return ItemProcessor { person ->
            person.copy(
                firstName = person.firstName.toUpperCase(), 
                lastName = person.lastName.toUpperCase()
            )
        }
    }
    @Bean
    fun personWriter(): ItemWriter<Person> {
        return ItemWriter { items ->
            items.forEach { println("Writing person: $it") }
        }
    }
    @Bean
    fun importUserJob(): Job {
        return jobBuilderFactory.get("importUserJob")
            .incrementer(RunIdIncrementer())
            .flow(step1())
            .end()
            .build()
    }
    @Bean
    fun step1(): Step {
        return stepBuilderFactory.get("step1")
            .chunk<Person, Person>(10)
            .reader(personReader())
            .processor(personProcessor())
            .writer(personWriter())
            .build()
    }
}
data class Person(
    val firstName: String = "",
    val lastName: String = ""
)

CSV file (resources/people.csv)

John,Doe
Jane,Doe

Auto-configuration in Spring Boot is a mechanism that automatically configures your Spring application based on dependencies found in the classpath and various conditions. It is one of the key features of Spring Boot, allowing you to minimize the amount of configuration required to run your application.

How autoconfiguration works:
Spring Boot analyzes the classes in your project's classpath and automatically creates and configures the necessary beans for your application. This process is controlled by the @EnableAutoConfiguration annotation, which is typically included in the @SpringBootApplication annotation.

Examples of autoconfiguration operation:

Database configuration:
If you have H2, HSQLDB or another supported database in your classpath, Spring Boot will automatically create beans for DataSource, EntityManager and configure them.

Web server configuration:
If you have dependencies on spring-boot-starter-web, Spring Boot will automatically configure a web server (such as Tomcat) and bundle it with your application.

Main autoconfiguration files and classes:

META-INF/spring.factories:
This file specifies the autoconfiguration classes that should be loaded. It is contained in the autoconfiguration JAR files.

Autoconfiguration classes:
Classes annotated with the @Configuration annotation and typically containing bean configuration logic based on the presence or absence of certain classes or beans in the application context.

Autoconfiguration definition:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

@Configuration
public class MyServiceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MyService myService() {
        return new MyService();
    }

}

Registering autoconfiguration in META-INF/spring.factories:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfiguration.MyServiceAutoConfiguration

Autoconfiguration conditions:
Spring Boot provides a variety of annotations that can be used to control when autoconfiguration should be applied:

@ConditionalOnClass:
Autoconfiguration is applied if the specified class is on the classpath.

@ConditionalOnMissingBean:
Autoconfiguration is applied if the bean is not present in the context.

@ConditionalOnProperty:
Autoconfiguration is applied if a specific property is set in application.properties or application.yml.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

@Configuration
@ConditionalOnClass(MyService.class)
public class MyServiceAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public MyService myService() {
        return new MyService();
    }
}

Disabling autoconfiguration:
Sometimes you may want to disable certain autoconfigurations. This can be done using the @SpringBootApplication or @EnableAutoConfiguration annotation.

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.boot.runApplication

@SpringBootApplication(exclude = [DataSourceAutoConfiguration::class])
class MyApplication

fun main(args: Array<String>) {
    SpringApplication.run(Application::class.java, *args)
}

In Spring Boot, you can use profiles to manage different configurations of your application, including different base URLs. Profiles allow you to create multiple configurations for different environments (e.g. development, testing, production) and switch between them without changing the main application code.

Steps to create multiple build variants with different base URLs:
Defining profiles in application.properties or application.yml
Create configuration files for each profile. For example, let's create three profiles: dev, test and prod, each with its own base URL.

# application-dev.properties
app.base.url=https://dev.example.com
# application-test.properties
app.base.url=https://test.example.com
#application-prod.properties
app.base.url=https://prod.example.com

Setting up the main configuration file:
In the main application.properties or application.yml file, you can define general settings and specify the default active profile (optional).

# application.properties
spring.profiles.active=dev

Reading configuration in your code:
Use the @Value or @ConfigurationProperties annotation to retrieve the base URL value from configuration files.

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Configuration

@Configuration
class AppConfig {
    @Value("\${app.base.url}")
    lateinit var baseUrl: String
}

Using profiles in code:
You can also use the @Profile annotation to enable or disable beans depending on the active profile.

import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Service

interface ExampleService {
    fun getBaseUrl(): String
}

@Service
@Profile("dev")
class DevExampleService(private val config: AppConfig) : ExampleService {
    override fun getBaseUrl(): String {
        return config.baseUrl
    }
}

@Service
@Profile("test")
class TestExampleService(private val config: AppConfig) : ExampleService {
    override fun getBaseUrl(): String {
        return config.baseUrl
    }
}

@Service
@Profile("prod")
class ProdExampleService(private val config: AppConfig) : ExampleService {
    override fun getBaseUrl(): String {
        return config.baseUrl
    }
}

Launching an application with the specified profile:
You can specify the active profile when starting an application via command line parameters, environment variables, or a configuration file.

Via command line parameters

$ java -jar myapp.jar --spring.profiles.active=prod

Via environment variables

$ export SPRING_PROFILES_ACTIVE=prod
$ java -jar myapp.jar

Spring WebFlux is a reactive web framework introduced in Spring 5 that allows you to build asynchronous and non-blocking web applications. WebFlux can run on various reactive engines such as Netty, Jetty, or Tomcat.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
}

Reactive and non-reactive code examples for Spring WebFlux and Spring Web (regular Spring MVC) using Kotlin. We will show you how to create a REST API for a simple CRUD application that works with users (User).

Example of non-reactive code for Spring Web (Spring MVC):

import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType

@Entity
data class User(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
    val email: String,
    val username: String
)
import org.springframework.data.jpa.repository.JpaRepository

interface UserRepository : JpaRepository<User, Long>
import org.springframework.stereotype.Service

@Service
class UserService(private val userRepository: UserRepository) {
    fun getAllUsers(): List<User> {
        return userRepository.findAll()
    }
    fun getUserById(id: Long): User? {
        return userRepository.findById(id).orElse(null)
    }
    fun createUser(user: User): User {
        return userRepository.save(user)
    }
    fun updateUser(id: Long, user: User): User? {
        return userRepository.findById(id).map {
            val updatedUser = it.copy(email = user.email, username = user.username)
            userRepository.save(updatedUser)
        }.orElse(null)
    }
    fun deleteUser(id: Long) {
        userRepository.deleteById(id)
    }
}
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/users")
class UserController(private val userService: UserService) {
    @GetMapping
    fun getAllUsers(): List<User> {
        return userService.getAllUsers()
    }
    @GetMapping("/{id}")
    fun getUserById(@PathVariable id: Long): User? {
        return userService.getUserById(id)
    }
    @PostMapping
    fun createUser(@RequestBody user: User): User {
        return userService.createUser(user)
    }
    @PutMapping("/{id}")
    fun updateUser(@PathVariable id: Long, @RequestBody user: User): User? {
        return userService.updateUser(id, user)
    }
    @DeleteMapping("/{id}")
    fun deleteUser(@PathVariable id: Long) {
        userService.deleteUser(id)
    }
}

Example reactive code for Spring WebFlux:

import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document

@Document(collection = "users")
data class User(
    @Id
    val id: String? = null,
    val email: String,
    val username: String
)
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import reactor.core.publisher.Mono

interface UserRepository : ReactiveMongoRepository<User, String>
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

@Service
class UserService(private val userRepository: UserRepository) {
    fun getAllUsers(): Flux<User> {
        return userRepository.findAll()
    }
    fun getUserById(id: String): Mono<User> {
        return userRepository.findById(id)
    }
    fun createUser(user: User): Mono<User> {
        return userRepository.save(user)
    }
    fun updateUser(id: String, user: User): Mono<User> {
        return userRepository.findById(id)
            .flatMap {
                val updatedUser = it.copy(email = user.email, username = user.username)
                userRepository.save(updatedUser)
            }
    }
    fun deleteUser(id: String): Mono<Void> {
        return userRepository.deleteById(id)
    }
}
import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

@RestController
@RequestMapping("/users")
class UserController(private val userService: UserService) {
    @GetMapping
    fun getAllUsers(): Flux<User> {
        return userService.getAllUsers()
    }
    @GetMapping("/{id}")
    fun getUserById(@PathVariable id: String): Mono<User> {
        return userService.getUserById(id)
    }
    @PostMapping
    fun createUser(@RequestBody user: User): Mono<User> {
        return userService.createUser(user)
    }
    @PutMapping("/{id}")
    fun updateUser(@PathVariable id: String, @RequestBody user: User): Mono<User> {
        return userService.updateUser(id, user)
    }
    @DeleteMapping("/{id}")
    fun deleteUser(@PathVariable id: String): Mono<Void> {
        return userService.deleteUser(id)
    }
}

The main differences are that the reactive approach uses Mono and Flux types to work with asynchronous data streams, which can improve the performance and scalability of the application, especially when working with a large number of simultaneous requests.

ApplicationContext is the central interface for configuring an application and accessing its components (beans). It is an extension of the BeanFactory interface, which provides the basic functionality of an inversion of control (IoC) container. The main difference between ApplicationContext and BeanFactory is in additional capabilities, such as:
Support for various types of events.
Possibility to load messages from localization files.
Support for declarative transaction management.

Example of creating ApplicationContext:
Creating an ApplicationContext from XML Configuration

<!-- beans.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="exampleBean" class="com.example.ExampleBean">
        <property name="message" value="Hello, Spring!" />
    </bean>
</beans>
import org.springframework.context.ApplicationContext
import org.springframework.context.support.ClassPathXmlApplicationContext

fun main() {
    val context: ApplicationContext = ClassPathXmlApplicationContext("beans.xml")
    val exampleBean: ExampleBean = context.getBean("exampleBean", ExampleBean::class.java)
    println(exampleBean.message)
}

Creating an ApplicationContext

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class AppConfig {
    @Bean
    fun exampleBean(): ExampleBean {
        return ExampleBean("Hello, Spring with Kotlin DSL!")
    }
}
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext

fun main() {
    val context: ApplicationContext = AnnotationConfigApplicationContext(AppConfig::class.java)
    val exampleBean: ExampleBean = context.getBean(ExampleBean::class.java)
    println(exampleBean.message)
}

BeanFactory is the central interface in Spring for managing the objects that make up your application. Although BeanFactory is the fundamental interface for the Spring container, its more functional descendant, ApplicationContext, is more commonly used. However, BeanFactory can be useful in some situations, such as when you need minimal memory usage or finer control over initialization.

Main methods of BeanFactory:

getBean:
Returns a bean instance by its name or type.

containsBean:
Checks if a bean with the given name exists.

isSingleton:
Checks whether the bean with the given name is a singleton.

isPrototype:
Checks whether the bean with the given name is a prototype.

getType:
Returns the type of a bean given its name.

getAliases:
Returns all aliases for the bean with the specified name.

Example of using BeanFactory with XML:

<!-- beans.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myBean" class="com.example.MyBean">
        <property name="name" value="Bean Name"/>
    </bean>
</beans>
import org.springframework.beans.factory.BeanFactory
import org.springframework.beans.factory.xml.XmlBeanFactory
import org.springframework.core.io.ClassPathResource

class MyBean {
    var name: String? = null
}

fun main() {
    // Loading Spring Configuration File
    val resource = ClassPathResource("beans.xml")
    val beanFactory: BeanFactory = XmlBeanFactory(resource)
    // Getting a bean by name
    val myBean = beanFactory.getBean("myBean") as MyBean
    println("Bean Name: ${myBean.name}")
    // Checking if a bin exists
    val containsBean = beanFactory.containsBean("myBean")
    println("Contains 'myBean': $containsBean")
    // Checking if a bean is a singleton
    val isSingleton = beanFactory.isSingleton("myBean")
    println("Is 'myBean' Singleton: $isSingleton")
    // Getting the bean type
    val beanType = beanFactory.getType("myBean")
    println("Bean Type: $beanType")
    // Get all aliases for a bean
    val aliases = beanFactory.getAliases("myBean")
    println("Bean Aliases: ${aliases.joinToString()}")
}
import org.springframework.context.ApplicationContext
import org.springframework.context.support.ClassPathXmlApplicationContext

fun main() {
    // Loading Spring Configuration File
    val context: ApplicationContext = ClassPathXmlApplicationContext("beans.xml")
    // Getting a bean by name
    val myBean = context.getBean("myBean") as MyBean
    println("Bean Name: ${myBean.name}")
    // Checking if a bin exists
    val containsBean = context.containsBean("myBean")
    println("Contains 'myBean': $containsBean")
    // Checking if a bean is a singleton
    val isSingleton = context.isSingleton("myBean")
    println("Is 'myBean' Singleton: $isSingleton")
    // Getting the bean type
    val beanType = context.getType("myBean")
    println("Bean Type: $beanType")
    // Get all aliases for a bean
    val aliases = context.getAliases("myBean")
    println("Bean Aliases: ${aliases.joinToString()}")
}

Example using annotations:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class AppConfig {
    @Bean
    fun myBean(): MyBean {
        return MyBean().apply {
            name = "Bean Name"
        }
    }
}
import org.springframework.beans.factory.BeanFactory
import org.springframework.stereotype.Service

@Service
class TestClass(
    private val beanFactory: BeanFactory
) {
    fun start() {
        val myBean = beanFactory.getBean("myBean") as GreetingHandler
    }
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext

fun main() {
    // Creating a context based on a configuration class
    val context = AnnotationConfigApplicationContext(AppConfig::class.java)
    // Getting a BeanFactory from a context
    val beanFactory = context.beanFactory
    // Getting a bean by name
    val myBean = beanFactory.getBean("myBean") as MyBean
    println("Bean Name: ${myBean.name}")
    // Checking if a bin exists
    val containsBean = beanFactory.containsBean("myBean")
    println("Contains 'myBean': $containsBean")
    // Checking if a bean is a singleton
    val isSingleton = beanFactory.isSingleton("myBean")
    println("Is 'myBean' Singleton: $isSingleton")
    // Getting the bean type
    val beanType = beanFactory.getType("myBean")
    println("Bean Type: $beanType")
    // Get all aliases for a bean
    val aliases = beanFactory.getAliases("myBean")
    println("Bean Aliases: ${aliases.joinToString()}")
}

Spring BOM (Bill Of Materials) is a dependency management mechanism provided by Spring to simplify version management of various artifacts (libraries) in the Spring ecosystem. BOM is used to ensure that all dependencies used in a project are compatible and consistent in their versions. Using BOM, you can specify one version of BOM, and all related libraries will automatically use the corresponding versions specified in the BOM.

Example using Spring BOM:

plugins {
    id 'org.springframework.boot' version '2.6.7'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}
dependencyManagement {
    imports {
        mavenBom "org.springframework.boot:spring-boot-dependencies:2.6.7"
    }
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    runtimeOnly 'com.h2database:h2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Example without using Spring BOM:

plugins {
    id 'org.springframework.boot' version '2.6.7'
    id 'java'
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:2.6.7'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.6.7'
    implementation 'org.springframework.boot:spring-boot-starter-security:2.6.7'
    runtimeOnly 'com.h2database:h2:1.4.200'
    testImplementation 'org.springframework.boot:spring-boot-starter-test:2.6.7'
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
class SecurityConfig {
    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http.authorizeExchange { exchanges ->
                exchanges.pathMatchers("/public/**")
                    .permitAll()
                    .anyExchange()
                    .authenticated()
            }
            .httpBasic().and()
            .formLogin().and()
            .csrf().disable()
            .build()
    }
    @Bean
    fun userDetailsService(): MapReactiveUserDetailsService {
        val user: UserDetails = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build()
        val admin: UserDetails = User.withDefaultPasswordEncoder()
            .username("admin")
            .password("password")
            .roles("ADMIN")
            .build()
        return MapReactiveUserDetailsService(user, admin)
    }
}

@Service
class SecuredService {
    @PreAuthorize("hasRole('ADMIN')")
    fun adminMethod(): Mono<String> {
        return Mono.just("Admin access granted")
    }
    @PreAuthorize("hasRole('USER')")
    fun userMethod(): Mono<String> {
        return Mono.just("User access granted")
    }
}

The @EnableWebFluxSecurity annotation is used to enable WebFlux security.
Added @EnableReactiveMethodSecurity annotation to enable method-level security.
The SecurityConfig configuration class defines two beans: securityWebFilterChain and userDetailsService.
The securityWebFilterChain method configures security rules to allow access to public URLs (e.g. /public/**) for all users, and require authentication for all other requests. Basic and Form authentication are also enabled, and CSRF protection is disabled.
The userDetailsService method creates two users with the roles “USER” and “ADMIN” using a simple password encryption method.
The SecuredService class contains methods protected by @PreAuthorize annotations, which restrict access to the methods based on user roles. The adminMethod method is only available to users with the “ADMIN” role, and the userMethod method is only available to users with the “USER” role.

Another example:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.crypto.password.NoOpPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.server.SecurityWebFilterChain
import reactor.core.publisher.Mono

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
class WebSecurityConfig(
    private val authenticationManager: AuthenticationManager,
    private val securityContextRepository: SecurityContextRepository
) {
    @Bean
    fun passwordEncoder(): PasswordEncoder {
        return NoOpPasswordEncoder.getInstance()
    }
    @Bean
    fun securityWebFilterChain(httpSecurity: ServerHttpSecurity): SecurityWebFilterChain {
        return httpSecurity.exceptionHandling()
            .authenticationEntryPoint { swe, _ ->
                Mono.fromRunnable {
                    swe.response.statusCode = HttpStatus.UNAUTHORIZED
                }
            }
            .accessDeniedHandler { swe, _ ->
                Mono.fromRunnable {
                    swe.response.statusCode = HttpStatus.FORBIDDEN
                }
            }
            .and()
            .csrf().disable()
            .formLogin().disable()
            .httpBasic().disable()
            .authenticationManager(authenticationManager)
            .securityContextRepository(securityContextRepository)
            .authorizeExchange()
            .pathMatchers("/", "/login", "/favicon.ico").permitAll()
            .pathMatchers("/controller").hasRole("ADMIN")
            .anyExchange().authenticated()
            .and()
            .build()
    }
}

The Spring Framework provides powerful and flexible tools for data validation, including annotations, built-in validation mechanisms, and support for custom validators.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import javax.validation.constraints.Email
import javax.validation.constraints.NotBlank
import javax.validation.constraints.Size

data class User(
    @field:NotBlank(message = "Name is mandatory")
    val name: String,
    @field:Email(message = "Email should be valid")
    @field:NotBlank(message = "Email is mandatory")
    val email: String,
    @field:Size(min = 8, message = "Password should be at least 8 characters")
    val password: String
)
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import javax.validation.Valid

@RestController
@RequestMapping("/api/users")
class UserController {
    @PostMapping("/register")
    fun registerUser(@Valid @RequestBody user: User): ResponseEntity<String> {
        // In a real application, this would be the user registration logic.
        return ResponseEntity("User registered successfully", HttpStatus.OK)
    }
}

Handling validation errors:

import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseStatus

@ControllerAdvice
class ValidationHandler {
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException::class)
    fun handleValidationExceptions(ex: MethodArgumentNotValidException): ResponseEntity<Map<String, String>> {
        val errors: MutableMap<String, String> = HashMap()
        ex.bindingResult.fieldErrors.forEach { error ->
            errors[error.field] = error.defaultMessage ?: "Invalid value"
        }
        return ResponseEntity(errors, HttpStatus.BAD_REQUEST)
    }
}

Example request:

POST /api/users/register
Content-Type: application/json
{
    "name": "",
    "email": "invalid-email",
    "password": "short"
}

Example answer:

{
    "name": "Name is mandatory",
    "email": "Email should be valid",
    "password": "Password should be at least 8 characters"
}

Memory leaks can be a serious problem for any application, including Spring applications. Identifying and fixing memory leaks requires a systematic approach, using profiling and monitoring tools, and knowledge of the typical causes of memory leaks in Java applications.

Basic steps to detect memory leaks:
Real-time memory monitoring
Using Profiling Tools
Memory dump analysis
Using logs and metrics
Finding typical problem areas

Real-time memory monitoring:
To monitor memory usage in real time, you can use tools like JVisualVM, JConsole, or external APM (Application Performance Monitoring) solutions like New Relic, Dynatrace, or Prometheus.

Using profiling tools:
Profilers help identify memory leaks by showing which objects are taking up a lot of memory and how they are related to each other.

JVisualVM:
Launch JVisualVM, which comes with the JDK.
Attach to a running JVM process.
Go to the Memory tab and click Heap Dump to create a memory dump.
Analyze the number of objects and their held sizes.

YourKit:
YourKit Java Profiler provides powerful memory analysis capabilities, including dump collection, leak analysis, and execution profiling.
Launch YourKit and connect to the JVM.
Create a memory dump and analyze it using built-in tools.

Memory dump analysis:
Analyzing memory dumps helps you understand which objects are not being freed from memory.

Eclipse Memory Analyzer (MAT):
Use Eclipse MAT to analyze memory dumps.
Open the memory dump and use tools like Leak Suspects Report and Histogram to identify problems.

Using logs and metrics:
Logs and metrics can help identify memory problems.

Spring Boot Actuator:
Use metrics such as jvm.memory.used to monitor memory usage.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
}
management.endpoints.web.exposure.include=*

Garbage Collection Logs:
Enable garbage collector logging in your application by adding the following JVM options:

-Xlog:gc*:file=gc.log:time

Finding typical problem areas:
Some common causes of memory leaks in Spring applications include

Unremovable cached objects:
Make sure that caches are configured correctly and that objects are removed from the cache as needed.

Incorrect use of singletons:
Pay attention to the use of singletons and check that they do not hold references to objects that should be garbage collected.

Session leaks:
Ensure that session objects are not persisted longer than necessary.

Non-released resources:
Check that all external resources (files, network connections, etc.) are closed correctly.

An example of using JVisualVM to detect memory leaks:

Running a Spring Boot application:
Run your Spring Boot application.
Connecting JVisualVM:
Open JVisualVM from the JDK.
Connect to your JVM process that runs the Spring Boot application.

Collecting memory data:
Go to the Monitor tab to monitor overall memory usage.
Go to the Sampler tab and start memory profiling.

Creating and analyzing a memory dump:
Go to the Heap Dump tab and create a memory dump.
Analyze the memory dump for large numbers of objects of the same type or objects that should not be in memory.

RouterFunction is a concept in Spring WebFlux that provides a functional way to define routes for handling HTTP requests.

Instead of annotations like @RequestMapping, a functional approach is used to configure routes. This allows you to create routes more declaratively and flexibly.

Key features of RouterFunction:

Functional style:
Uses functional interfaces to define routes and request handlers.

Declarativeness:
Routes and handlers are defined in code, making routing easier to manage.

Asynchrony:
Fully supports asynchronous and non-blocking programming, making it suitable for high-load applications.

RequestPredicate:
An interface in Spring WebFlux that is used to check whether an incoming HTTP request meets certain conditions. It plays a key role in functional programming routing in WebFlux, helping to determine which handlers should handle specific requests based on various criteria such as path, HTTP method, headers, and request parameters.

Key features of RequestPredicate:

Checking query conditions:
Allows you to check whether requests match certain conditions, such as paths, methods, headers, and request parameters.

Combination of conditions:
You can combine multiple conditions using the and(), or(), and negate() methods to create complex predicates.

Usage in routing:
Used in conjunction with RouterFunction to define routes in a functional style.

andRoute:
used to chain multiple routes in a functional routing style. This allows multiple routes to be chained together so that they can be handled by a single routing configuration.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

@Component
class GreetingHandler {
    fun hello(request: ServerRequest): Mono<ServerResponse> {
        return ServerResponse.ok()
            .contentType(MediaType.TEXT_PLAIN)
            .bodyValue("Hello, Spring WebFlux!")
    }
    fun index(request: ServerRequest): Mono<ServerResponse> {
        return ServerResponse.ok()
            .contentType(MediaType.TEXT_PLAIN)
            .bodyValue("Welcome to the home page!")
    }
}
import com.example.handler.GreetingHandler
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.RequestPredicate
import org.springframework.web.reactive.function.server.RequestPredicates.*
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions.route
import org.springframework.web.reactive.function.server.ServerResponse

@Configuration
class GreetingRouter {
    @Bean
    fun route(greetingHandler: GreetingHandler): RouterFunction<ServerResponse> {
        val helloRoute: RequestPredicate = GET("/hello")
             .and(accept(MediaType.TEXT_PLAIN))
        return route(helloRoute, greetingHandler::hello)
            .andRoute(GET("/"), greetingHandler::index)
    }
}

or

import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Mono

@RestController
@RequestMapping(produces = [MediaType.TEXT_PLAIN_VALUE])
class GreetingHandler {
    @GetMapping("/hello")
    fun hello(): Mono<String> {
        return Mono.just("Hello, Spring WebFlux!")
    }
    @GetMapping("/")
    fun index(): Mono<String> {
        return Mono.just("Welcome to the home page!")
    }
}

more examples:

data class User(
    val id: Long,
    val name: String,
    val email: String
)
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

@Service
class UserService {
    private val users = listOf(
        User(1, "John Doe", "[email protected]"),
        User(2, "Jane Doe", "[email protected]")
    )
    fun getAllUsers(): Flux<User> {
        return Flux.fromIterable(users)
    }
    fun getUserById(id: Long): Mono<User> {
        return Mono.justOrEmpty(users.find { it.id == id })
    }
}
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

@Component
class UserHandler(private val userService: UserService) {
    fun getAllUsers(request: ServerRequest): Mono<ServerResponse> {
        return ServerResponse.ok().body(userService.getAllUsers(), User::class.java)
    }
    fun getUserById(request: ServerRequest): Mono<ServerResponse> {
        val userId = request.pathVariable("id").toLong()
        return userService.getUserById(userId)
            .flatMap { user -> ServerResponse.ok().bodyValue(user) }
            .switchIfEmpty(ServerResponse.notFound().build())
    }
    fun getUsersByHeader(request: ServerRequest): Mono<ServerResponse> {
        val headerValue = request.headers().firstHeader("X-USER-ROLE") ?: return ServerResponse.badRequest().build()
        return ServerResponse.ok().body(userService.getUsersByRole(headerValue), User::class.java)
    }
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.function.server.RequestPredicates.*
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions.route
import org.springframework.web.reactive.function.server.ServerResponse

@Configuration
class RouterConfig {
    @Bean
    fun userRoutes(userHandler: UserHandler): RouterFunction<ServerResponse> {
        return route()
            .GET("/users", userHandler::getAllUsers)
            .GET("/users/{id}", userHandler::getUserById)
            .GET("/users/role", headers("X-USER-ROLE"), userHandler::getUsersByHeader)
            .build()
    }
}

RequestPredicate:
An interface that defines a condition that a request must meet. Used to create routing conditions.

Examples of predicates:

GET(“/users”):
Checks that the request uses the GET method and the path “/users”.

GET(“/users/{id}”):
Checks that the request uses the GET method and the path “/users/{id}”.

headers(“X-USER-ROLE”):
Checks that the request contains the “X-USER-ROLE” header.

Combining predicates:
Predicates can be combined using the and(), or(), and negate() methods. For example, you can create a complex condition that checks for a path, the presence of a header, and the HTTP method.

To execute background tasks on a timer in Spring, you can use the @Scheduled annotation, which allows you to schedule methods to execute at a specified frequency.

Enabling scheduled task support:
First, you need to enable support for the @Scheduled annotation in your application. This is done using the @EnableScheduling annotation in one of the configuration classes or in the main application class.

import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.annotation.EnableScheduling

@Configuration
@EnableScheduling
class SchedulerConfig

Create a service for background tasks:
Create a service that will contain scheduled methods. These methods should be annotated with @Scheduled.

import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
import java.time.LocalDateTime

@Service
class ScheduledTasks {

@Scheduled(fixedRate = 5000)
fun reportCurrentTime() {
println("Current time: ${LocalDateTime.now()}")
}
}

@Scheduled annotation parameters:

fixedRate:
Specifies a fixed interval between method runs, in milliseconds. For example, fixedRate = 300000 means that the method will be executed every 5 minutes (300000 milliseconds).

fixedDelay:
Defines the interval between the completion of the last method run and the start of the next one. For example, fixedDelay = 300000 will also run the method every 5 minutes, but only after the previous run has completed.

@Scheduled(fixedDelay = 5000)
fun taskWithFixedDelay() {
    println("The task with fixedDelay is executed: ${LocalDateTime.now()}")
}

initialDelay:
Specifies the delay before the method is first executed, in milliseconds. For example, initialDelay = 10000 means that the method will be executed 10 seconds after the application is launched.

@Scheduled(initialDelay = 10000, fixedRate = 5000)
fun taskWithInitialDelay() {
    println("Task with initialDelay is executed: ${LocalDateTime.now()}")
}

cron:
Allows you to set a schedule in cron format. For example, cron = "0 0 * * * *" will run the method every hour.

Example of using cron:
This method will be executed daily at 00:00.

@Scheduled(cron = "0 0/1 * 1/1 * ?")
fun taskWithCronExpression() {
    println("The task with the cron expression is executed every minute: ${LocalDateTime.now()}")
}

Thread pool setup:
By default, tasks are executed in a single thread, which can be limiting if you have multiple long running tasks. To use a thread pool, you can configure TaskScheduler.

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler

@Configuration
class SchedulerConfig {

    @Bean
    fun taskScheduler(): ThreadPoolTaskScheduler {
        val scheduler = ThreadPoolTaskScheduler()
        scheduler.poolSize = 10
        scheduler.setThreadNamePrefix("scheduled-task-pool-")
        return scheduler
    }
}

Now tasks will be executed in a separate thread pool, which allows you to run multiple tasks in parallel.

To import CSV files into Spring, you can use various libraries such as OpenCSV or Apache Commons CSV. Here is an example of using OpenCSV to import data from a CSV file into a Spring application.

dependencies {
    implementation 'com.opencsv:opencsv:5.7.1'
}
data class Person(
    val id: Long,
    val name: String,
    val age: Int,
    val email: String
)
import com.opencsv.bean.CsvToBeanBuilder
import org.springframework.stereotype.Service
import org.springframework.web.multipart.MultipartFile
import java.io.InputStreamReader

@Service
class CsvService {

    fun importCsv(file: MultipartFile): List<Person> {
        val reader = InputStreamReader(file.inputStream)
        val csvToBean = CsvToBeanBuilder<Person>(reader)
            .withType(Person::class.java)
            .withIgnoreLeadingWhiteSpace(true)
            .build()
        return csvToBean.parse()
    }
}
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile

@RestController
class CsvController(private val csvService: CsvService) {

    @PostMapping("/import")
    fun importCsv(@RequestParam("file") file: MultipartFile): ResponseEntity<List<Person>> {
        return try {
            val people = csvService.importCsv(file)
            ResponseEntity(people, HttpStatus.OK)
        } catch (e: Exception) {
            ResponseEntity(HttpStatus.BAD_REQUEST)
        }
    }
}

You can now test your API by sending a POST request to /import with the uploaded CSV file.

Example of CSV file

id,name,age,email
1,John Doe,30,[email protected]
2,Jane Smith,25,[email protected]

Copyright: Roman Kryvolapov