spring-boot

Spring Boot 3.x - Production-Ready Java Framework

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "spring-boot" with this command: npx skills add bobmatnyc/claude-mpm-skills/bobmatnyc-claude-mpm-skills-spring-boot

Spring Boot 3.x - Production-Ready Java Framework

Overview

Spring Boot is an opinionated Java framework for building production-ready applications with minimal configuration. It provides auto-configuration, embedded servers, and production-ready features like health checks and metrics.

Key Features:

  • Auto-configuration (sensible defaults)

  • Embedded servers (Tomcat, Jetty, Undertow)

  • Dependency Injection with @Autowired

  • Spring Data JPA for database access

  • Spring Security for authentication/authorization

  • Actuator for production monitoring

  • Built-in testing support

Requirements:

  • Java 17+ (Spring Boot 3.x requires Java 17 minimum)

  • Maven or Gradle

Quick Start:

Create project from Spring Initializr

curl https://start.spring.io/starter.zip
-d type=maven-project
-d language=java
-d bootVersion=3.2.0
-d dependencies=web,data-jpa,postgresql,lombok,actuator
-d name=myapp
-o myapp.zip && unzip myapp.zip

Run the application

cd myapp ./mvnw spring-boot:run

Project Structure

src/ ├── main/ │ ├── java/com/example/myapp/ │ │ ├── MyappApplication.java # Main class │ │ ├── config/ # @Configuration classes │ │ ├── controller/ # @RestController classes │ │ ├── service/ # @Service classes │ │ ├── repository/ # @Repository interfaces │ │ ├── model/ # Entity classes │ │ ├── dto/ # Data Transfer Objects │ │ └── exception/ # Exception handlers │ └── resources/ │ ├── application.yml # Configuration │ └── application-{profile}.yml # Profile-specific config └── test/ └── java/com/example/myapp/ # Test classes

Core Annotations

Application Setup

// Main application class @SpringBootApplication // Combines @Configuration, @EnableAutoConfiguration, @ComponentScan public class MyappApplication { public static void main(String[] args) { SpringApplication.run(MyappApplication.class, args); } }

Dependency Injection

// Constructor injection (recommended) @Service public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder;

// @Autowired optional on single constructor (Spring 4.3+)
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
    this.userRepository = userRepository;
    this.passwordEncoder = passwordEncoder;
}

}

// With Lombok @Service @RequiredArgsConstructor // Generates constructor for final fields public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; }

// Field injection (avoid in production code) @Service public class UserService { @Autowired private UserRepository userRepository; // Harder to test }

Component Stereotypes

@Component // Generic component @Service // Business logic layer @Repository // Data access layer (enables exception translation) @Controller // MVC controller (returns views) @RestController // REST API controller (returns JSON) @Configuration // Configuration class with @Bean methods

REST Controllers

Basic Controller

@RestController @RequestMapping("/api/v1/users") @RequiredArgsConstructor public class UserController {

private final UserService userService;

// GET /api/v1/users
@GetMapping
public ResponseEntity<List<UserDto>> getAllUsers() {
    return ResponseEntity.ok(userService.findAll());
}

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

// POST /api/v1/users
@PostMapping
public ResponseEntity<UserDto> createUser(@Valid @RequestBody CreateUserRequest request) {
    UserDto created = userService.create(request);
    URI location = ServletUriComponentsBuilder
        .fromCurrentRequest()
        .path("/{id}")
        .buildAndExpand(created.getId())
        .toUri();
    return ResponseEntity.created(location).body(created);
}

// PUT /api/v1/users/{id}
@PutMapping("/{id}")
public ResponseEntity<UserDto> updateUser(
        @PathVariable Long id,
        @Valid @RequestBody UpdateUserRequest request) {
    return ResponseEntity.ok(userService.update(id, request));
}

// DELETE /api/v1/users/{id}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
    userService.delete(id);
    return ResponseEntity.noContent().build();
}

// GET /api/v1/users/search?email=test@example.com
@GetMapping("/search")
public ResponseEntity<List<UserDto>> searchUsers(
        @RequestParam(required = false) String email,
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "20") int size) {
    return ResponseEntity.ok(userService.search(email, page, size));
}

}

Request/Response DTOs

// Request DTO with validation @Data public class CreateUserRequest { @NotBlank(message = "Email is required") @Email(message = "Invalid email format") private String email;

@NotBlank(message = "Name is required")
@Size(min = 2, max = 100, message = "Name must be 2-100 characters")
private String name;

@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;

}

// Response DTO @Data @Builder public class UserDto { private Long id; private String email; private String name; private LocalDateTime createdAt; private LocalDateTime updatedAt;

public static UserDto fromEntity(User user) {
    return UserDto.builder()
        .id(user.getId())
        .email(user.getEmail())
        .name(user.getName())
        .createdAt(user.getCreatedAt())
        .updatedAt(user.getUpdatedAt())
        .build();
}

}

Service Layer

@Service @RequiredArgsConstructor @Transactional(readOnly = true) // Default to read-only transactions public class UserService {

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;

public List<UserDto> findAll() {
    return userRepository.findAll().stream()
        .map(UserDto::fromEntity)
        .collect(Collectors.toList());
}

public Optional<UserDto> findById(Long id) {
    return userRepository.findById(id)
        .map(UserDto::fromEntity);
}

@Transactional  // Read-write transaction
public UserDto create(CreateUserRequest request) {
    if (userRepository.existsByEmail(request.getEmail())) {
        throw new EmailAlreadyExistsException(request.getEmail());
    }

    User user = User.builder()
        .email(request.getEmail())
        .name(request.getName())
        .passwordHash(passwordEncoder.encode(request.getPassword()))
        .build();

    return UserDto.fromEntity(userRepository.save(user));
}

@Transactional
public UserDto update(Long id, UpdateUserRequest request) {
    User user = userRepository.findById(id)
        .orElseThrow(() -> new UserNotFoundException(id));

    if (request.getName() != null) {
        user.setName(request.getName());
    }
    if (request.getEmail() != null) {
        user.setEmail(request.getEmail());
    }

    return UserDto.fromEntity(userRepository.save(user));
}

@Transactional
public void delete(Long id) {
    if (!userRepository.existsById(id)) {
        throw new UserNotFoundException(id);
    }
    userRepository.deleteById(id);
}

public List<UserDto> search(String email, int page, int size) {
    Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
    Page<User> users = email != null
        ? userRepository.findByEmailContainingIgnoreCase(email, pageable)
        : userRepository.findAll(pageable);

    return users.stream()
        .map(UserDto::fromEntity)
        .collect(Collectors.toList());
}

}

Repository Layer (Spring Data JPA)

Basic Repository

@Repository public interface UserRepository extends JpaRepository<User, Long> {

// Derived query methods
Optional&#x3C;User> findByEmail(String email);
boolean existsByEmail(String email);
List&#x3C;User> findByNameContainingIgnoreCase(String name);

// Paginated queries
Page&#x3C;User> findByEmailContainingIgnoreCase(String email, Pageable pageable);

// Custom JPQL query
@Query("SELECT u FROM User u WHERE u.createdAt > :date AND u.active = true")
List&#x3C;User> findActiveUsersCreatedAfter(@Param("date") LocalDateTime date);

// Native SQL query
@Query(value = "SELECT * FROM users WHERE email ILIKE %:email%", nativeQuery = true)
List&#x3C;User> searchByEmail(@Param("email") String email);

// Modifying query
@Modifying
@Query("UPDATE User u SET u.active = false WHERE u.lastLoginAt &#x3C; :date")
int deactivateInactiveUsers(@Param("date") LocalDateTime date);

}

Entity Class

@Entity @Table(name = "users") @Data @Builder @NoArgsConstructor @AllArgsConstructor public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true)
private String email;

@Column(nullable = false)
private String name;

@Column(name = "password_hash", nullable = false)
private String passwordHash;

@Column(nullable = false)
@Builder.Default
private boolean active = true;

@Column(name = "created_at", nullable = false, updatable = false)
@CreationTimestamp
private LocalDateTime createdAt;

@Column(name = "updated_at", nullable = false)
@UpdateTimestamp
private LocalDateTime updatedAt;

// Relationships
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List&#x3C;Post> posts = new ArrayList&#x3C;>();

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
    name = "user_roles",
    joinColumns = @JoinColumn(name = "user_id"),
    inverseJoinColumns = @JoinColumn(name = "role_id")
)
@Builder.Default
private Set&#x3C;Role> roles = new HashSet&#x3C;>();

}

Configuration

application.yml

spring: application: name: myapp

datasource: url: jdbc:postgresql://localhost:5432/mydb username: ${DB_USERNAME:postgres} password: ${DB_PASSWORD:password} hikari: maximum-pool-size: 10 minimum-idle: 5 connection-timeout: 30000

jpa: hibernate: ddl-auto: validate # none, validate, update, create, create-drop show-sql: false properties: hibernate: format_sql: true default_schema: public

profiles: active: ${SPRING_PROFILES_ACTIVE:dev}

server: port: ${PORT:8080} servlet: context-path: /api

Actuator endpoints

management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: when_authorized

Custom properties

app: jwt: secret: ${JWT_SECRET:your-secret-key} expiration-ms: 86400000

Profile-Specific Configuration

application-dev.yml

spring: jpa: show-sql: true h2: console: enabled: true

logging: level: com.example.myapp: DEBUG org.springframework.web: DEBUG


application-prod.yml

spring: jpa: show-sql: false properties: hibernate: generate_statistics: false

logging: level: com.example.myapp: INFO org.springframework.web: WARN

Configuration Properties Class

@Configuration @ConfigurationProperties(prefix = "app.jwt") @Data public class JwtProperties { private String secret; private long expirationMs; }

// Usage @Service @RequiredArgsConstructor public class JwtService { private final JwtProperties jwtProperties;

public String generateToken(User user) {
    return Jwts.builder()
        .setSubject(user.getEmail())
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpirationMs()))
        .signWith(Keys.hmacShaKeyFor(jwtProperties.getSecret().getBytes()))
        .compact();
}

}

Exception Handling

Global Exception Handler

@RestControllerAdvice @Slf4j public class GlobalExceptionHandler {

// Handle validation errors
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity&#x3C;ErrorResponse> handleValidationErrors(MethodArgumentNotValidException ex) {
    List&#x3C;String> errors = ex.getBindingResult()
        .getFieldErrors()
        .stream()
        .map(error -> error.getField() + ": " + error.getDefaultMessage())
        .collect(Collectors.toList());

    ErrorResponse response = ErrorResponse.builder()
        .status(HttpStatus.BAD_REQUEST.value())
        .message("Validation failed")
        .errors(errors)
        .timestamp(LocalDateTime.now())
        .build();

    return ResponseEntity.badRequest().body(response);
}

// Handle not found exceptions
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity&#x3C;ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
    ErrorResponse response = ErrorResponse.builder()
        .status(HttpStatus.NOT_FOUND.value())
        .message(ex.getMessage())
        .timestamp(LocalDateTime.now())
        .build();

    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}

// Handle business logic exceptions
@ExceptionHandler(BusinessException.class)
public ResponseEntity&#x3C;ErrorResponse> handleBusinessException(BusinessException ex) {
    ErrorResponse response = ErrorResponse.builder()
        .status(HttpStatus.CONFLICT.value())
        .message(ex.getMessage())
        .timestamp(LocalDateTime.now())
        .build();

    return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
}

// Catch-all handler
@ExceptionHandler(Exception.class)
public ResponseEntity&#x3C;ErrorResponse> handleAllExceptions(Exception ex) {
    log.error("Unexpected error occurred", ex);

    ErrorResponse response = ErrorResponse.builder()
        .status(HttpStatus.INTERNAL_SERVER_ERROR.value())
        .message("An unexpected error occurred")
        .timestamp(LocalDateTime.now())
        .build();

    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}

}

// Error response DTO @Data @Builder public class ErrorResponse { private int status; private String message; private List<String> errors; private LocalDateTime timestamp; }

// Custom exceptions public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String resource, Long id) { super(String.format("%s not found with id: %d", resource, id)); } }

public class UserNotFoundException extends ResourceNotFoundException { public UserNotFoundException(Long id) { super("User", id); } }

Spring Security

Security Configuration (Spring Security 6.x)

@Configuration @EnableWebSecurity @EnableMethodSecurity @RequiredArgsConstructor public class SecurityConfig {

private final JwtAuthenticationFilter jwtAuthFilter;
private final UserDetailsService userDetailsService;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf.disable())
        .sessionManagement(session -> session
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/v1/auth/**").permitAll()
            .requestMatchers("/actuator/health").permitAll()
            .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated())
        .authenticationProvider(authenticationProvider())
        .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

@Bean
public AuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(userDetailsService);
    provider.setPasswordEncoder(passwordEncoder());
    return provider;
}

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

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
        throws Exception {
    return config.getAuthenticationManager();
}

}

JWT Filter

@Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtService jwtService;
private final UserDetailsService userDetailsService;

@Override
protected void doFilterInternal(
        HttpServletRequest request,
        HttpServletResponse response,
        FilterChain filterChain) throws ServletException, IOException {

    final String authHeader = request.getHeader("Authorization");

    if (authHeader == null || !authHeader.startsWith("Bearer ")) {
        filterChain.doFilter(request, response);
        return;
    }

    final String jwt = authHeader.substring(7);
    final String userEmail = jwtService.extractUsername(jwt);

    if (userEmail != null &#x26;&#x26; SecurityContextHolder.getContext().getAuthentication() == null) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);

        if (jwtService.isTokenValid(jwt, userDetails)) {
            UsernamePasswordAuthenticationToken authToken =
                new UsernamePasswordAuthenticationToken(
                    userDetails,
                    null,
                    userDetails.getAuthorities());

            authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authToken);
        }
    }

    filterChain.doFilter(request, response);
}

}

Actuator Endpoints

Built-in endpoints

management: endpoints: web: exposure: include: health,info,metrics,prometheus,env base-path: /actuator endpoint: health: show-details: when_authorized probes: enabled: true # Kubernetes liveness/readiness probes info: env: enabled: true

Application info

info: app: name: ${spring.application.name} version: '@project.version@' java: version: ${java.version}

Common Actuator Endpoints:

  • GET /actuator/health

  • Application health

  • GET /actuator/health/liveness

  • Kubernetes liveness probe

  • GET /actuator/health/readiness

  • Kubernetes readiness probe

  • GET /actuator/info

  • Application information

  • GET /actuator/metrics

  • Metrics list

  • GET /actuator/metrics/{name}

  • Specific metric

  • GET /actuator/prometheus

  • Prometheus format metrics

Testing

Unit Testing Controllers

@WebMvcTest(UserController.class) class UserControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private UserService userService;

@Autowired
private ObjectMapper objectMapper;

@Test
void shouldReturnUserById() throws Exception {
    UserDto user = UserDto.builder()
        .id(1L)
        .email("test@example.com")
        .name("Test User")
        .build();

    when(userService.findById(1L)).thenReturn(Optional.of(user));

    mockMvc.perform(get("/api/v1/users/1"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.id").value(1))
        .andExpect(jsonPath("$.email").value("test@example.com"));
}

@Test
void shouldReturn404WhenUserNotFound() throws Exception {
    when(userService.findById(999L)).thenReturn(Optional.empty());

    mockMvc.perform(get("/api/v1/users/999"))
        .andExpect(status().isNotFound());
}

@Test
void shouldCreateUser() throws Exception {
    CreateUserRequest request = new CreateUserRequest();
    request.setEmail("new@example.com");
    request.setName("New User");
    request.setPassword("password123");

    UserDto created = UserDto.builder()
        .id(1L)
        .email("new@example.com")
        .name("New User")
        .build();

    when(userService.create(any())).thenReturn(created);

    mockMvc.perform(post("/api/v1/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isCreated())
        .andExpect(jsonPath("$.email").value("new@example.com"));
}

}

Integration Testing

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) @Transactional class UserIntegrationTest {

@Autowired
private TestRestTemplate restTemplate;

@Autowired
private UserRepository userRepository;

@Test
void shouldCreateAndRetrieveUser() {
    CreateUserRequest request = new CreateUserRequest();
    request.setEmail("integration@test.com");
    request.setName("Integration Test");
    request.setPassword("password123");

    ResponseEntity&#x3C;UserDto> createResponse = restTemplate.postForEntity(
        "/api/v1/users", request, UserDto.class);

    assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);
    assertThat(createResponse.getBody()).isNotNull();
    assertThat(createResponse.getBody().getEmail()).isEqualTo("integration@test.com");

    Long userId = createResponse.getBody().getId();
    ResponseEntity&#x3C;UserDto> getResponse = restTemplate.getForEntity(
        "/api/v1/users/" + userId, UserDto.class);

    assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
    assertThat(getResponse.getBody().getName()).isEqualTo("Integration Test");
}

}

Repository Testing

@DataJpaTest class UserRepositoryTest {

@Autowired
private UserRepository userRepository;

@Autowired
private TestEntityManager entityManager;

@Test
void shouldFindByEmail() {
    User user = User.builder()
        .email("test@example.com")
        .name("Test")
        .passwordHash("hash")
        .build();
    entityManager.persistAndFlush(user);

    Optional&#x3C;User> found = userRepository.findByEmail("test@example.com");

    assertThat(found).isPresent();
    assertThat(found.get().getName()).isEqualTo("Test");
}

}

Best Practices

  1. Use Constructor Injection

// Prefer constructor injection with final fields @Service @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; // final = immutable }

  1. Layer Separation

// Controller -> Service -> Repository // DTOs for API layer, Entities for persistence layer // Never expose entities directly in REST responses

  1. Transaction Management

@Service @Transactional(readOnly = true) // Default read-only public class UserService {

@Transactional  // Write transaction
public void updateUser() { }

}

  1. Configuration Externalization

Use environment variables for secrets

spring: datasource: password: ${DB_PASSWORD} # From environment

  1. Error Handling

// Use @RestControllerAdvice for global exception handling // Return consistent error responses // Never expose internal details in production

Resources

Related Skills

When using Spring Boot, consider these complementary skills:

  • mongodb: NoSQL database integration with Spring Data MongoDB

  • docker: Containerizing Spring Boot applications

  • kubernetes: Deploying Spring Boot microservices

  • postgresql: Relational database patterns with JPA

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

drizzle-orm

No summary provided by upstream source.

Repository SourceNeeds Review
General

pydantic

No summary provided by upstream source.

Repository SourceNeeds Review
General

playwright-e2e-testing

No summary provided by upstream source.

Repository SourceNeeds Review
General

tailwind-css

No summary provided by upstream source.

Repository SourceNeeds Review