Spring Boot Annotations Guide

Essential Spring Boot annotations with code examples: stereotype annotations, dependency injection, configuration, request mapping, and validation.

1. Stereotype Annotations

// @RestController = @Controller + @ResponseBody
@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    // Constructor injection (preferred over @Autowired field injection)
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping
    public List<UserDto> getAll() {
        return userService.findAll();
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserDto> getById(@PathVariable Long id) {
        return userService.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public UserDto create(@Valid @RequestBody CreateUserRequest req) {
        return userService.create(req);
    }

    @PutMapping("/{id}")
    public UserDto update(@PathVariable Long id, @Valid @RequestBody UpdateUserRequest req) {
        return userService.update(id, req);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable Long id) {
        userService.delete(id);
    }
}

2. @Service & @Repository

@Service
@Transactional(readOnly = true)
public class UserService {

    private final UserRepository repo;
    private final PasswordEncoder encoder;

    public UserService(UserRepository repo, PasswordEncoder encoder) {
        this.repo = repo;
        this.encoder = encoder;
    }

    public List<UserDto> findAll() {
        return repo.findAll().stream().map(UserDto::from).toList();
    }

    @Transactional
    public UserDto create(CreateUserRequest req) {
        if (repo.existsByEmail(req.email())) {
            throw new ConflictException("Email already in use");
        }
        User user = new User(req.name(), req.email(), encoder.encode(req.password()));
        return UserDto.from(repo.save(user));
    }
}

// @Repository adds exception translation (DataAccessException)
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    boolean existsByEmail(String email);
    Optional<User> findByEmail(String email);
    List<User> findByRoleAndIsActiveTrue(String role);
}

3. @Value & @ConfigurationProperties

// @Value โ€” inject single property
@Component
public class JwtConfig {
    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration:3600}") // default 3600
    private long expiration;

    @Value("${app.allowed-origins}")
    private List<String> allowedOrigins;
}

// @ConfigurationProperties โ€” type-safe config binding (preferred)
@Configuration
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {
    @NotBlank
    private String name;

    private Security security = new Security();
    private Database database = new Database();

    @Data
    public static class Security {
        private String jwtSecret;
        private Duration jwtExpiration = Duration.ofHours(1);
        private List<String> allowedOrigins = List.of();
    }

    @Data
    public static class Database {
        private int maxPoolSize = 10;
        private Duration connectionTimeout = Duration.ofSeconds(30);
    }
    // getters/setters
}

4. @Component & @Bean

// @Component โ€” generic Spring-managed bean
@Component
public class SlugGenerator {
    public String generate(String input) {
        return input.toLowerCase().replaceAll("[^a-z0-9]+", "-");
    }
}

// @Bean inside @Configuration โ€” explicit bean declaration
@Configuration
public class AppConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12);
    }

    @Bean
    public ObjectMapper objectMapper() {
        return JsonMapper.builder()
            .addModule(new JavaTimeModule())
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .build();
    }

    @Bean
    @ConditionalOnProperty(name = "feature.cache.enabled", havingValue = "true")
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("users", "articles");
    }
}

5. Validation Annotations

import jakarta.validation.constraints.*;

public record CreateUserRequest(
    @NotBlank @Size(min = 1, max = 100)
    String name,

    @NotBlank @Email
    String email,

    @NotBlank @Size(min = 8, max = 128)
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).+$",
             message = "Must contain upper, lower, and digit")
    String password,

    @Min(0) @Max(150)
    Integer age,

    @NotEmpty
    List<@NotBlank String> roles
) {}

6. Annotations Quick Reference

AnnotationLayerPurpose
@SpringBootApplicationAppMain entry point
@RestControllerWebREST endpoint class
@RequestMappingWebURL prefix for class/method
@GetMapping/@PostMappingWebHTTP method mapping
@ServiceServiceBusiness logic bean
@RepositoryDataDAO bean + exception translation
@TransactionalDataTransaction boundary
@AutowiredAnyDependency injection (avoid field)
@ValueAnyInject property value
@ConfigurationPropertiesConfigType-safe config binding
@ProfileAnyConditional by Spring profile