6.3 KiB
Spring Framework Conversion Guide
When This Applies
This guide applies when the Java source contains imports matching org.springframework.*.
This covers Spring Boot, Spring MVC, Spring Data, and Spring Security.
Key Rules
1. SpringApplication.run — spread CLI args
In Kotlin, String[] varargs must be spread with the * operator.
- Java:
SpringApplication.run(App.class, args); - Kotlin:
SpringApplication.run(App::class.java, *args)
2. Constructor injection over @Autowired
Kotlin's primary constructor makes constructor injection natural. When a class has a
single constructor, Spring auto-discovers it — remove @Autowired.
3. Stereotype annotations
@Component, @Service, @RestController, and @Repository target the class.
Preserve these annotations exactly. No annotation site target is needed.
4. @Value annotation
Use @Value on constructor parameters. Escape $ in SpEL expressions to prevent
Kotlin string template interpretation:
@Value("\${app.name}") val appName: String
5. @ConfigurationProperties
Convert to a data class only if the properties are immutable. For mutable
configuration, use a regular class with lateinit var.
6. Spring Data repositories
Interface declarations convert directly. Replace Optional<T> return types with
nullable T? in Kotlin for idiomatic usage.
7. @RequestMapping / @GetMapping / @PostMapping etc.
Preserve exactly. Where Java uses array initializer syntax for annotation parameters,
use arrayOf() in Kotlin.
8. @Transactional
Preserve exactly. The class must remain open because Spring creates proxies via
subclassing. Do not make @Transactional classes final.
9. @Bean methods in @Configuration classes
@Bean methods must be open so that Spring can override them in CGLIB proxies.
Alternatively, apply the allopen compiler plugin with a Spring preset, which makes
annotated classes and their members open automatically.
Examples
Example 1: Spring Boot Application Main Class
Java:
package com.acme;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Kotlin:
package com.acme
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
open class Application
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
Key changes:
mainbecomes a top-level function (no companion object needed).runApplication<T>is a Spring Boot Kotlin extension that replacesSpringApplication.run(T::class.java, *args).- The
*argsspread operator is required for the varargs parameter.
Example 2: REST Controller with Constructor Injection
Java:
package com.acme.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public UserDto getUser(@PathVariable Long id) {
return userService.findById(id);
}
@GetMapping
public List<UserDto> getAllUsers() {
return userService.findAll();
}
}
Kotlin:
package com.acme.web
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api/users")
class UserController(
private val userService: UserService
) {
@GetMapping("/{id}")
fun getUser(@PathVariable id: Long): UserDto? {
return userService.findById(id)
}
@GetMapping
fun getAllUsers(): List<UserDto> {
return userService.findAll()
}
}
Key changes:
@Autowiredis removed — Spring auto-discovers the single constructor.- The
Autowiredimport is removed because it is no longer referenced. - Constructor parameter becomes a
private valin the primary constructor. - Return type
UserDtobecomesUserDto?where the service may return null.
Example 3: @ConfigurationProperties Class
Java:
package com.acme.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {
private String host;
private int port = 587;
private String username;
private String password;
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
Kotlin (mutable config with lateinit var):
package com.acme.config
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component
@Component
@ConfigurationProperties(prefix = "app.mail")
open class MailProperties {
lateinit var host: String
var port: Int = 587
lateinit var username: String
lateinit var password: String
}
Key changes:
- Getters and setters are replaced by Kotlin properties.
lateinit varis used for requiredStringproperties that Spring populates after construction.portkeeps its default value and uses a regularvar(lateinitdoes not support primitive types).- The class is
openso that Spring can create a CGLIB proxy for it.