Spring Basics 3 min read

Essential Spring Boot Annotations in Kotlin: A Developer's Guide

Discover how to effectively use Spring Boot annotations in your Kotlin applications. This comprehensive guide covers essential annotations, provides real-world examples, and highlights best practices for clean, maintainable code.

Spring Boot annotations are powerful tools that simplify your code and provide essential functionality. In this guide, we'll explore the most commonly used annotations in Kotlin Spring Boot applications and see how they work with practical examples.

Key Takeaways

  • Understanding core Spring Boot annotations and their purposes
  • How to use annotations effectively in Kotlin
  • Best practices for annotation usage
  • Common pitfalls and how to avoid them

Class-Level Annotations

Let's start with the most essential class-level annotations:

@SpringBootApplication
class MyApplication

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

The @SpringBootApplication annotation combines three annotations:

  • @Configuration: Marks the class as a source of bean definitions
  • @EnableAutoConfiguration: Enables Spring Boot's auto-configuration
  • @ComponentScan: Scans for components in the current package and sub-packages

Component Annotations

Spring Boot provides several annotations for defining components:

// Service component
@Service
class UserService(private val userRepository: UserRepository) {
    fun getUser(id: Long): User = userRepository.findById(id)
        .orElseThrow { UserNotFoundException(id) }
}

// Repository component
@Repository
interface UserRepository : JpaRepository<User, Long>

// Controller component
@RestController
@RequestMapping("/api/users")
class UserController(private val userService: UserService) {
    @GetMapping("/{id}")
    fun getUser(@PathVariable id: Long) = userService.getUser(id)
}

// Configuration component
@Configuration
class SecurityConfig {
    @Bean
    fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()
}

Request Mapping Annotations

Spring MVC provides various annotations for handling HTTP requests:

@RestController
@RequestMapping("/api/products")
class ProductController(private val productService: ProductService) {
    
    @GetMapping
    fun getAllProducts(): List<Product> = productService.findAll()
    
    @GetMapping("/{id}")
    fun getProduct(@PathVariable id: Long): Product = productService.findById(id)
    
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    fun createProduct(@RequestBody @Valid product: ProductDto): Product =
        productService.create(product)
    
    @PutMapping("/{id}")
    fun updateProduct(
        @PathVariable id: Long,
        @RequestBody @Valid product: ProductDto
    ): Product = productService.update(id, product)
    
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    fun deleteProduct(@PathVariable id: Long) = productService.delete(id)
}

Dependency Injection Annotations

Kotlin's concise syntax works beautifully with Spring's dependency injection:

// Constructor injection (preferred in Kotlin)
@Service
class OrderService(
    private val orderRepository: OrderRepository,
    private val paymentService: PaymentService
)

// Field injection (use sparingly)
@Service
class LegacyService {
    @Autowired
    private lateinit var repository: LegacyRepository
}

// Qualifier usage
@Service
class PaymentProcessor(
    @Qualifier("stripePayment")
    private val paymentService: PaymentService
)

Configuration Properties

Spring Boot's configuration properties work well with Kotlin data classes:

@ConfigurationProperties(prefix = "app")
@ConstructorBinding
data class AppProperties(
    val apiKey: String,
    val maxConnections: Int = 10,
    val timeoutMs: Long = 5000,
    val features: Features
) {
    data class Features(
        val audit: Boolean = false,
        val metrics: Boolean = true
    )
}

// Usage in application.properties:
# app.api-key=your-api-key
# app.max-connections=20
# app.timeout-ms=3000
# app.features.audit=true

Testing Annotations

Spring Boot provides comprehensive testing support:

@SpringBootTest
class UserServiceIntegrationTest {
    
    @Autowired
    private lateinit var userService: UserService
    
    @Test
    fun `should create user successfully`() {
        val user = User(name = "John Doe", email = "[email protected]")
        val savedUser = userService.createUser(user)
        
        assertNotNull(savedUser.id)
        assertEquals(user.name, savedUser.name)
    }
}

@WebMvcTest(UserController::class)
class UserControllerTest {
    
    @Autowired
    private lateinit var mockMvc: MockMvc
    
    @MockkBean
    private lateinit var userService: UserService
    
    @Test
    fun `should return user when exists`() {
        val user = User(id = 1, name = "John")
        every { userService.getUser(1) } returns user
        
        mockMvc.get("/api/users/1")
            .andExpect {
                status { isOk() }
                jsonPath("$.name") { value("John") }
            }
    }
}

Common Pitfalls and Best Practices

    • Prefer constructor injection in Kotlin
    • It makes dependencies explicit and supports immutability
    • Be specific with component scanning to improve startup time
    • Use data classes with validation
    • Provide default values when appropriate

Configuration Properties

@ConfigurationProperties(prefix = "app")
@Validated
data class AppConfig(
    @field:NotBlank
    val apiKey: String,
    val timeout: Duration = Duration.ofSeconds(30)
)

Component Scanning

@SpringBootApplication(scanBasePackages = ["com.example.myapp"])
class MyApplication

Constructor Injection vs Field Injection

// Good
@Service
class UserService(private val repository: UserRepository)

// Avoid
@Service
class UserService {
    @Autowired
    private lateinit var repository: UserRepository
}

Next Steps

  • Explore more specialized annotations like @Transactional, @Cacheable, and @Scheduled
  • Learn about custom annotations
  • Understand Spring Boot's conditional annotations
  • Study Spring Security annotations

Remember: Annotations are powerful tools, but they should be used judiciously. Always consider whether an annotation is truly necessary and what implications it has for your application's behavior and maintainability.