| name | spring-boot-expert |
| version | 1.0.0 |
| description | Expert-level Spring Boot, Spring Framework, REST APIs, and microservices development |
| category | frameworks |
| tags | ["spring-boot","java","spring-framework","rest-api","microservices"] |
| allowed-tools | ["Read","Write","Edit","Bash(mvn:*, gradle:*, java:*)"] |
Spring Boot Expert
Expert guidance for Spring Boot development, Spring Framework, building REST APIs, and microservices architecture.
Core Concepts
Spring Boot Fundamentals
- Auto-configuration
- Dependency injection
- Spring Boot Starters
- Application properties
- Profiles and configuration
- Spring Boot Actuator
Spring Framework
- Spring Core (IoC, DI)
- Spring Data JPA
- Spring Security
- Spring Web MVC
- Spring AOP
- Spring Transaction Management
Microservices
- Service discovery
- API Gateway
- Circuit breakers
- Distributed tracing
- Configuration management
Spring Boot Application
@SpringBootApplication
@EnableJpaAuditing
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Entity
@Table(name = "users")
@EntityListeners(AuditingEntityListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
@CreatedDate
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<Post> posts = new ArrayList<>();
}
@Entity
@Table(name = "posts")
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
String content;
User author;
LocalDateTime createdAt;
}
REST API Controller
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping
public ResponseEntity<Page<UserDto>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "id") String sortBy,
@RequestParam(defaultValue = "ASC") Sort.Direction direction
) {
Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sortBy));
Page<UserDto> users = userService.findAll(pageable);
return ResponseEntity.ok(users);
}
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<UserDto> createUser(
@Valid @RequestBody UserCreateDto userDto
) {
UserDto created = userService.create(userDto);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(created.getId())
.toUri();
ResponseEntity.created(location).body(created);
}
ResponseEntity<UserDto> {
userService.update(id, userDto)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
ResponseEntity<Void> {
(userService.delete(id)) {
ResponseEntity.noContent().build();
}
ResponseEntity.notFound().build();
}
}
{}
String password
) {}
{}
Service Layer
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final UserMapper userMapper;
public Page<UserDto> findAll(Pageable pageable) {
return userRepository.findAll(pageable)
.map(userMapper::toDto);
}
public Optional<UserDto> findById(Long id) {
return userRepository.findById(id)
.map(userMapper::toDto);
}
public Optional<UserDto> findByEmail(String email) {
return userRepository.findByEmail(email)
.map(userMapper::toDto);
}
@Transactional
public UserDto create(UserCreateDto dto) {
if (userRepository.existsByEmail(dto.email())) {
throw new DuplicateEmailException("Email already exists");
}
User user = new User();
user.setEmail(dto.email());
user.setPassword(passwordEncoder.encode(dto.password()));
User saved = userRepository.save(user);
return userMapper.toDto(saved);
}
Optional<UserDto> {
userRepository.findById(id)
.map(user -> {
(dto.email() != ) {
user.setEmail(dto.email());
}
userMapper.toDto(user);
});
}
{
(userRepository.existsById(id)) {
userRepository.deleteById(id);
;
}
;
}
}
<User, Long> {
Optional<User> ;
;
List<User> ;
}
{
UserDto ;
User ;
}
Spring Security with JWT
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/actuator/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
@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
) ServletException, IOException {
request.getHeader();
(authHeader == || !authHeader.startsWith()) {
filterChain.doFilter(request, response);
;
}
authHeader.substring();
jwtService.extractUsername(jwt);
(userEmail != && SecurityContextHolder.getContext().getAuthentication() == ) {
userDetailsService.loadUserByUsername(userEmail);
(jwtService.isTokenValid(jwt, userDetails)) {
(
userDetails,
,
userDetails.getAuthorities()
);
authToken.setDetails( ().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
{
String secretKey;
jwtExpiration;
String {
extractClaim(token, Claims::getSubject);
}
String {
buildToken( <>(), userDetails, jwtExpiration);
}
{
extractUsername(token);
username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
{
extractExpiration(token).before( ());
}
Date {
extractClaim(token, Claims::getExpiration);
}
String {
Jwts
.builder()
.setClaims(extraClaims)
.setSubject(userDetails.getUsername())
.setIssuedAt( (System.currentTimeMillis()))
.setExpiration( (System.currentTimeMillis() + expiration))
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
.compact();
}
<T> T {
extractAllClaims(token);
claimsResolver.apply(claims);
}
Claims {
Jwts
.parserBuilder()
.setSigningKey(getSignInKey())
.build()
.parseClaimsJws(token)
.getBody();
}
Key {
[] keyBytes = Decoders.BASE64.decode(secretKey);
Keys.hmacShaKeyFor(keyBytes);
}
}
Exception Handling
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
ex.getMessage(),
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(DuplicateEmailException.class)
public ResponseEntity<ErrorResponse> handleDuplicateEmail(DuplicateEmailException ex) {
ErrorResponse error = new ErrorResponse(
"DUPLICATE_EMAIL",
ex.getMessage(),
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationErrorResponse> handleValidation(
MethodArgumentNotValidException ex
) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
ValidationErrorResponse response = new ValidationErrorResponse(
"VALIDATION_ERROR",
,
errors,
LocalDateTime.now()
);
ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
ResponseEntity<ErrorResponse> {
(
,
,
LocalDateTime.now()
);
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
{}
{}
Configuration
spring:
application:
name: user-service
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: validate
show-sql: false
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
flyway:
enabled: true
baseline-on-migrate: true
server:
port: 8080
error:
include-message: always
include-binding-errors: always
jwt:
secret: ${JWT_SECRET:your-secret-key-here}
expiration: 3600000
logging:
level:
root: INFO
com.example: DEBUG
Testing
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(locations = "classpath:application-test.properties")
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository.deleteAll();
}
@Test
void shouldCreateUser() throws Exception {
UserCreateDto dto = new UserCreateDto("test@example.com", "password123");
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(dto)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.email").value("test@example.com"));
}
@Test
void shouldGetUser() throws Exception {
User user = createTestUser();
mockMvc.perform(get("/api/users/{id}", user.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.email").value(user.getEmail()));
}
@Test
void shouldReturnNotFoundForInvalidId Exception {
mockMvc.perform(get())
.andExpect(status().isNotFound());
}
User {
();
user.setEmail();
user.setPassword();
userRepository.save(user);
}
}
{
UserRepository userRepository;
PasswordEncoder passwordEncoder;
UserMapper userMapper;
UserService userService;
{
(, );
();
(, , LocalDateTime.now());
(userRepository.existsByEmail(dto.email())).thenReturn();
(passwordEncoder.encode(dto.password())).thenReturn();
(userRepository.save(any(User.class))).thenReturn(user);
(userMapper.toDto(user)).thenReturn(expected);
userService.create(dto);
assertNotNull(result);
assertEquals(expected.email(), result.email());
verify(userRepository).save(any(User.class));
}
}
Best Practices
- Use constructor injection
- Separate concerns (Controller/Service/Repository)
- Implement proper exception handling
- Use DTOs for API layer
- Write comprehensive tests
- Use database migrations (Flyway/Liquibase)
- Implement security properly
- Use profiles for different environments
- Enable Spring Boot Actuator for monitoring
- Use connection pooling
- Implement caching where appropriate
- Follow RESTful conventions
Anti-Patterns
❌ Field injection
❌ Business logic in controllers
❌ No exception handling
❌ Exposing entities directly
❌ Hardcoded configuration
❌ No transaction management
❌ Missing validation
Resources