--- name: spring-boot-application description: Build enterprise Spring Boot applications with annotations, dependency injection, data persistence, REST controllers, and security. Use when developing Spring applications, managing beans, implementing services, and configuring Spring Boot projects. --- # Spring Boot Application ## Overview Develop production-ready Spring Boot applications with proper annotation-based configuration, dependency injection, REST controllers, JPA data persistence, service layers, and security implementation following Spring conventions. ## When to Use - Building Spring Boot REST APIs - Implementing service-oriented architectures - Configuring data persistence with JPA - Managing dependency injection - Implementing Spring Security - Building microservices with Spring Boot ## Instructions ### 1. **Spring Boot Project Setup** ```xml 4.0.0 com.example api-service 1.0.0 org.springframework.boot spring-boot-starter-parent 3.1.0 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-security org.postgresql postgresql io.jsonwebtoken jjwt 0.12.3 org.springframework.boot spring-boot-maven-plugin ``` ### 2. **Entity Models with JPA Annotations** ```java // User.java package com.example.model; import jakarta.persistence.*; import java.time.LocalDateTime; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @Entity @Table(name = "users", indexes = { @Index(name = "idx_email", columnList = "email", unique = true), @Index(name = "idx_role", columnList = "role") }) public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.UUID) private String id; @Column(unique = true, nullable = false) private String email; @Column(nullable = false) private String passwordHash; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; @Enumerated(EnumType.STRING) @Column(nullable = false) private Role role = Role.USER; @Column(name = "is_active") private Boolean isActive = true; @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt = LocalDateTime.now(); @Column(name = "updated_at") private LocalDateTime updatedAt = LocalDateTime.now(); @OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true) private Collection posts; // Getters and setters @Override public String getUsername() { return email; } @Override public String getPassword() { return passwordHash; } @Override public Collection getAuthorities() { return java.util.List.of(new SimpleGrantedAuthority(role.name())); } @Override public boolean isEnabled() { return isActive; } public enum Role { USER, ADMIN } } // Post.java @Entity @Table(name = "posts") public class Post { @Id @GeneratedValue(strategy = GenerationType.UUID) private String id; @Column(nullable = false) private String title; @Column(nullable = false, columnDefinition = "TEXT") private String content; @Column(nullable = false) private Boolean published = false; @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "user_id") private User author; @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt = LocalDateTime.now(); @Column(name = "updated_at") private LocalDateTime updatedAt = LocalDateTime.now(); // Getters and setters } ``` ### 3. **Repository Layer with Spring Data JPA** ```java // UserRepository.java package com.example.repository; import com.example.model.User; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository public interface UserRepository extends JpaRepository { Optional findByEmail(String email); @Query("SELECT u FROM User u WHERE u.email LIKE %:search% OR u.firstName LIKE %:search%") Page searchUsers(String search, Pageable pageable); Page findByRole(User.Role role, Pageable pageable); } // PostRepository.java @Repository public interface PostRepository extends JpaRepository { Page findByAuthorAndPublishedTrue(User author, Pageable pageable); @Query(value = "SELECT p FROM Post p WHERE p.published = true ORDER BY p.createdAt DESC", countQuery = "SELECT COUNT(p) FROM Post p WHERE p.published = true") Page findPublishedPosts(Pageable pageable); Long countByAuthorId(String authorId); } ``` ### 4. **Service Layer with Business Logic** ```java // UserService.java package com.example.service; import com.example.model.User; import com.example.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @Service @RequiredArgsConstructor @Transactional public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; public User createUser(String email, String password, String firstName, String lastName) { if (userRepository.findByEmail(email).isPresent()) { throw new IllegalArgumentException("Email already exists"); } User user = new User(); user.setEmail(email); user.setPasswordHash(passwordEncoder.encode(password)); user.setFirstName(firstName); user.setLastName(lastName); return userRepository.save(user); } @Transactional(readOnly = true) public Page getUsers(String search, Pageable pageable) { if (search != null && !search.isBlank()) { return userRepository.searchUsers(search, pageable); } return userRepository.findAll(pageable); } @Transactional(readOnly = true) public Optional getUserById(String id) { return userRepository.findById(id); } public User updateUser(String id, String firstName, String lastName) { User user = userRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("User not found")); if (firstName != null) user.setFirstName(firstName); if (lastName != null) user.setLastName(lastName); return userRepository.save(user); } public void deleteUser(String id) { userRepository.deleteById(id); } } // PostService.java @Service @RequiredArgsConstructor @Transactional public class PostService { private final PostRepository postRepository; private final UserRepository userRepository; public Post createPost(String userId, String title, String content) { User author = userRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("User not found")); Post post = new Post(); post.setTitle(title); post.setContent(content); post.setAuthor(author); return postRepository.save(post); } @Transactional(readOnly = true) public Page getPublishedPosts(Pageable pageable) { return postRepository.findPublishedPosts(pageable); } public Post publishPost(String postId) { Post post = postRepository.findById(postId) .orElseThrow(() -> new IllegalArgumentException("Post not found")); post.setPublished(true); return postRepository.save(post); } } ``` ### 5. **REST Controllers with Request/Response Handling** ```java // UserController.java package com.example.controller; import com.example.model.User; import com.example.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/users") @RequiredArgsConstructor public class UserController { private final UserService userService; @GetMapping public ResponseEntity> getUsers( @RequestParam(required = false) String search, Pageable pageable) { Page users = userService.getUsers(search, pageable); return ResponseEntity.ok(users); } @GetMapping("/{id}") public ResponseEntity getUser(@PathVariable String id) { return userService.getUserById(id) .map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.notFound().build()); } @PostMapping public ResponseEntity createUser(@RequestBody CreateUserRequest request) { User user = userService.createUser( request.getEmail(), request.getPassword(), request.getFirstName(), request.getLastName() ); return ResponseEntity.status(HttpStatus.CREATED).body(user); } @PatchMapping("/{id}") @PreAuthorize("@securityService.isCurrentUser(#id)") public ResponseEntity updateUser( @PathVariable String id, @RequestBody UpdateUserRequest request) { User user = userService.updateUser(id, request.getFirstName(), request.getLastName()); return ResponseEntity.ok(user); } @DeleteMapping("/{id}") @PreAuthorize("hasRole('ADMIN')") public ResponseEntity deleteUser(@PathVariable String id) { userService.deleteUser(id); return ResponseEntity.noContent().build(); } } // DTO classes @Data class CreateUserRequest { private String email; private String password; private String firstName; private String lastName; } @Data class UpdateUserRequest { private String firstName; private String lastName; } ``` ### 6. **Spring Security Configuration** ```java // SecurityConfig.java package com.example.config; import com.example.security.JwtAuthenticationFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests() .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/api/users/**").authenticated() .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } } // JwtAuthenticationFilter.java @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider tokenProvider; private final UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String token = getJwtFromRequest(request); if (token != null && tokenProvider.validateToken(token)) { String userId = tokenProvider.getUserIdFromToken(token); UserDetails userDetails = userDetailsService.loadUserByUsername(userId); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception ex) { logger.error("Could not set user authentication", ex); } filterChain.doFilter(request, response); } private String getJwtFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (bearerToken != null && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } } ``` ### 7. **Application Configuration** ```yaml # application.yml spring: application: name: api-service datasource: url: jdbc:postgresql://localhost:5432/mydb username: ${DB_USER} password: ${DB_PASSWORD} driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: validate properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect security: jwt: secret: ${JWT_SECRET} expiration: 86400000 server: port: 8080 servlet: context-path: / ``` ## Best Practices ### ✅ DO - Use dependency injection for loose coupling - Implement service layer for business logic - Use repositories for data access - Leverage Spring Security for authentication - Use @Transactional for transaction management - Validate input in controllers - Return appropriate HTTP status codes - Use DTOs for request/response mapping - Implement proper exception handling - Use Spring's @Async for async operations ### ❌ DON'T - Put business logic in controllers - Access database directly in controllers - Store secrets in configuration files - Use eager loading for large relationships - Ignore transaction boundaries - Return database entities in API responses - Implement authentication in controllers - Use raw SQL without parameterized queries - Forget to validate user input ## Complete Example ```java @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } @RestController @RequestMapping("/api") public class SimpleController { @GetMapping("/health") public ResponseEntity health() { return ResponseEntity.ok("OK"); } } ```