| 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
<!-- pom.xml -->
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>api-service</artifactId>
<version>1.0.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.12.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. Entity Models with JPA Annotations
// 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<Post> posts;
// Getters and setters
@Override
public String getUsername() {
return email;
}
@Override
public String getPassword() {
return passwordHash;
}
@Override
public Collection<? extends GrantedAuthority> 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
// 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<User, String> {
Optional<User> findByEmail(String email);
@Query("SELECT u FROM User u WHERE u.email LIKE %:search% OR u.firstName LIKE %:search%")
Page<User> searchUsers(String search, Pageable pageable);
Page<User> findByRole(User.Role role, Pageable pageable);
}
// PostRepository.java
@Repository
public interface PostRepository extends JpaRepository<Post, String> {
Page<Post> 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<Post> findPublishedPosts(Pageable pageable);
Long countByAuthorId(String authorId);
}
4. Service Layer with Business Logic
// 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<User> getUsers(String search, Pageable pageable) {
if (search != null && !search.isBlank()) {
return userRepository.searchUsers(search, pageable);
}
return userRepository.findAll(pageable);
}
@Transactional(readOnly = true)
public Optional<User> 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<Post> 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
// 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<Page<User>> getUsers(
@RequestParam(required = false) String search,
Pageable pageable) {
Page<User> users = userService.getUsers(search, pageable);
return ResponseEntity.ok(users);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable String id) {
return userService.getUserById(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<User> 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<User> 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<Void> 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
// 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
# 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
@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<String> health() {
return ResponseEntity.ok("OK");
}
}