| name | baks-spring-boot-best-practices |
| description | Spring Boot best practices for Kotlin — injection, transactions, proxy annotations, JPA entities, naming, and REST API design. Use when writing or reviewing Spring Boot code in Kotlin. |
Spring Boot Best Practices (Kotlin)
Injection
Use constructor injection, not field injection.
@Service
class PaymentService {
@Autowired
lateinit var repository: PaymentRepository
}
@Service
class PaymentService(
private val repository: PaymentRepository,
)
Proxy Annotations
Proxy annotations (@Transactional, @Cacheable, @CacheEvict, @CachePut, @Async) must not be placed on private methods — the Spring proxy cannot intercept them.
@Service
class InvoiceService {
fun createInvoice() { persistInvoice() }
@Transactional
private fun persistInvoice() { }
}
@Service
class InvoiceService {
@Transactional
fun createInvoice() { persistInvoice() }
private fun persistInvoice() { }
}
Avoid self-invocation between proxy-annotated methods. Calls via this bypass the proxy — inject a separate bean instead.
@Service
class ReportService {
@Transactional
fun generate(id: Long) { archive(id) }
@Async
fun archive(id: Long) { }
}
@Service
class ReportService(private val archiveService: ArchiveService) {
@Transactional
fun generate(id: Long) { archiveService.archive(id) }
}
@Service
class ArchiveService {
@Async
fun archive(id: Long) { }
}
Configuration
@Configuration classes must not hold mutable state (exceptions: @Value and @ConfigurationProperties fields).
@Configuration
class MailConfiguration {
var retries = 0
@Bean fun mailClient(): MailClient = MailClient()
}
@Configuration
class MailConfiguration {
@Bean
fun mailClient(properties: MailProperties): MailClient =
MailClient(properties.host, properties.port)
}
@Bean methods must live in @Configuration classes, not in @Component.
Exceptions
Custom exceptions must extend RuntimeException, not Exception.
class PaymentFailedException(message: String) : Exception(message)
class PaymentFailedException(message: String) : RuntimeException(message)
JPA
Kotlin data class must not be used as JPA entities — data class is final and prevents lazy-loading proxies.
@Entity
data class UserEntity(@Id val id: Long, val email: String)
@Entity
class UserEntity(
@Id var id: Long? = null,
var email: String,
)
All @Entity classes must declare an @Id field.
@Transactional belongs in the service layer, not in controllers.
Domain and entity packages must not import org.springframework.* — keep the domain model independent of Spring.
Naming
| Annotation | Suffix |
|---|
@Service | Service |
@Repository | Repository |
@Controller / @RestController | Controller |
@ControllerAdvice / @RestControllerAdvice | ExceptionHandler or Advice |
@ConfigurationProperties | Properties |
REST API
Use specific HTTP method annotations — avoid @RequestMapping where @GetMapping, @PostMapping, etc. apply.
No trailing slash on mapping paths: /users, not /users/.
GET endpoints must return something — not Unit/void. If it is a command, use POST.
Do not expose JPA entities from REST — use dedicated DTOs.
Controllers must not inject repositories directly — go through a service.
@RestController
class UserController(private val userRepository: UserRepository)
@RestController
class UserController(private val userService: UserService)