Introduction to Spring Boot
Spring Boot has revolutionized Java backend development by providing a convention-over-configuration approach that simplifies the creation of production-grade applications. This comprehensive guide will explore Spring Boot’s powerful features, helping you build robust, scalable, and maintainable backend applications.
The Spring ecosystem offers a rich set of tools and libraries that make it an excellent choice for enterprise applications. Spring Boot’s auto-configuration, embedded servers, and production-ready features make it ideal for microservices and monolithic applications alike. Its integration with Spring Data, Spring Security, and Spring Cloud provides a complete solution for modern backend development.
Project Structure and Architecture
Spring Boot follows a layered architecture pattern that promotes separation of concerns and maintainability. Let’s examine a comprehensive project structure that follows best practices.
project-root/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── app/
│ │ │ ├── config/
│ │ │ │ ├── SecurityConfig.java
│ │ │ │ ├── SwaggerConfig.java
│ │ │ │ └── WebConfig.java
│ │ │ ├── controller/
│ │ │ │ ├── UserController.java
│ │ │ │ └── ProductController.java
│ │ │ ├── model/
│ │ │ │ ├── User.java
│ │ │ │ └── Product.java
│ │ │ ├── repository/
│ │ │ │ ├── UserRepository.java
│ │ │ │ └── ProductRepository.java
│ │ │ ├── service/
│ │ │ │ ├── UserService.java
│ │ │ │ └── ProductService.java
│ │ │ ├── dto/
│ │ │ │ ├── UserDTO.java
│ │ │ │ └── ProductDTO.java
│ │ │ ├── exception/
│ │ │ │ ├── GlobalExceptionHandler.java
│ │ │ │ └── ResourceNotFoundException.java
│ │ │ └── Application.java
│ │ └── resources/
│ │ ├── application.yml
│ │ ├── application-dev.yml
│ │ ├── application-prod.yml
│ │ └── logback-spring.xml
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── app/
│ ├── controller/
│ ├── service/
│ └── repository/
├── pom.xml
└── README.md
This structure follows Spring Boot’s best practices for project organization. The main package contains the application code, with separate packages for configuration, controllers, models, repositories, services, and DTOs. The resources directory contains configuration files for different environments. The test directory mirrors the main structure for unit and integration tests.
Models and Database Integration
Spring Data JPA provides a powerful ORM implementation for Java applications. Let’s implement comprehensive models with proper relationships and validation.
package com.example.app.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "users")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@Size(min = 3, max = 50)
@Column(unique = true)
private String username;
@NotBlank
@Email
@Column(unique = true)
private String email;
@NotBlank
@Size(min = 8)
private String password;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set roles = new HashSet<>();
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private Set products = new HashSet<>();
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
@PrePersist
@PreUpdate
private void validate() {
if (password.length() < 8) {
throw new IllegalArgumentException("Password must be at least 8 characters long");
}
}
}
@Entity
@Table(name = "products")
@Data
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@Size(min = 3, max = 100)
private String name;
@NotBlank
@Column(columnDefinition = "TEXT")
private String description;
@NotNull
@DecimalMin("0.01")
private BigDecimal price;
@NotNull
@Min(0)
private Integer stock;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
private Set reviews = new HashSet<>();
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}
@Entity
@Table(name = "reviews")
@Data
public class Review {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Min(1)
@Max(5)
private Integer rating;
@Column(columnDefinition = "TEXT")
private String comment;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
private Product product;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}
These models demonstrate Spring Data JPA’s powerful ORM capabilities. The User model includes proper validation, password constraints, and role management. The Product model includes proper relationships and validation. The Review model shows how to implement many-to-one relationships with proper constraints. Each model includes auditing fields and proper validation.
Repositories and Services
Spring Data JPA provides powerful repository abstractions, while Spring’s service layer handles business logic. Let’s implement comprehensive repositories and services.
package com.example.app.repository;
import com.example.app.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository {
Optional findByUsername(String username);
Optional findByEmail(String email);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
@Query("SELECT u FROM User u JOIN FETCH u.roles WHERE u.username = :username")
Optional findByUsernameWithRoles(@Param("username") String username);
}
package com.example.app.service;
import com.example.app.dto.UserDTO;
import com.example.app.exception.ResourceNotFoundException;
import com.example.app.model.User;
import com.example.app.repository.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public UserDTO createUser(UserDTO userDTO) {
if (userRepository.existsByUsername(userDTO.getUsername())) {
throw new IllegalArgumentException("Username already exists");
}
if (userRepository.existsByEmail(userDTO.getEmail())) {
throw new IllegalArgumentException("Email already exists");
}
User user = new User();
user.setUsername(userDTO.getUsername());
user.setEmail(userDTO.getEmail());
user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
return convertToDTO(userRepository.save(user));
}
public UserDTO getUserById(Long id) {
return userRepository.findById(id)
.map(this::convertToDTO)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
}
public List getAllUsers() {
return userRepository.findAll().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
public UserDTO updateUser(Long id, UserDTO userDTO) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
if (!user.getUsername().equals(userDTO.getUsername())
&& userRepository.existsByUsername(userDTO.getUsername())) {
throw new IllegalArgumentException("Username already exists");
}
if (!user.getEmail().equals(userDTO.getEmail())
&& userRepository.existsByEmail(userDTO.getEmail())) {
throw new IllegalArgumentException("Email already exists");
}
user.setUsername(userDTO.getUsername());
user.setEmail(userDTO.getEmail());
if (userDTO.getPassword() != null) {
user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
}
return convertToDTO(userRepository.save(user));
}
public void deleteUser(Long id) {
if (!userRepository.existsById(id)) {
throw new ResourceNotFoundException("User not found");
}
userRepository.deleteById(id);
}
private UserDTO convertToDTO(User user) {
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setUsername(user.getUsername());
dto.setEmail(user.getEmail());
dto.setCreatedAt(user.getCreatedAt());
dto.setUpdatedAt(user.getUpdatedAt());
return dto;
}
}
This implementation shows how to create comprehensive repositories and services in Spring Boot. The UserRepository demonstrates custom query methods and relationship fetching. The UserService handles business logic, including user creation, retrieval, updating, and deletion. The implementation includes proper transaction management, validation, and error handling.
Controllers and DTOs
Spring MVC provides a powerful framework for building REST APIs. Let’s implement comprehensive controllers and DTOs.
package com.example.app.controller;
import com.example.app.dto.UserDTO;
import com.example.app.service.UserService;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.net.URI;
import java.util.List;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public ResponseEntity createUser(@Valid @RequestBody UserDTO userDTO) {
UserDTO createdUser = userService.createUser(userDTO);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(createdUser.getId())
.toUri();
return ResponseEntity.created(location).body(createdUser);
}
@GetMapping("/{id}")
public ResponseEntity getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUserById(id));
}
@GetMapping
public ResponseEntity> getAllUsers() {
return ResponseEntity.ok(userService.getAllUsers());
}
@PutMapping("/{id}")
public ResponseEntity updateUser(
@PathVariable Long id,
@Valid @RequestBody UserDTO userDTO
) {
return ResponseEntity.ok(userService.updateUser(id, userDTO));
}
@DeleteMapping("/{id}")
public ResponseEntity deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}
package com.example.app.dto;
import jakarta.validation.constraints.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class UserDTO {
private Long id;
@NotBlank
@Size(min = 3, max = 50)
private String username;
@NotBlank
@Email
private String email;
@NotBlank
@Size(min = 8)
private String password;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
This implementation shows how to create comprehensive controllers and DTOs in Spring Boot. The UserController demonstrates RESTful endpoints for user management, including proper HTTP status codes and location headers. The UserDTO includes validation annotations and proper field constraints. The implementation follows REST best practices and includes proper error handling.
Security Configuration
Spring Security provides comprehensive security features for Spring applications. Let’s implement a robust security configuration.
package com.example.app.config;
import com.example.app.security.JwtAuthenticationFilter;
import com.example.app.security.JwtAuthenticationEntryPoint;
import com.example.app.service.UserDetailsServiceImpl;
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.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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
@EnableMethodSecurity
public class SecurityConfig {
private final UserDetailsServiceImpl userDetailsService;
private final JwtAuthenticationEntryPoint unauthorizedHandler;
public SecurityConfig(
UserDetailsServiceImpl userDetailsService,
JwtAuthenticationEntryPoint unauthorizedHandler
) {
this.userDetailsService = userDetailsService;
this.unauthorizedHandler = unauthorizedHandler;
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig
) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests()
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(
jwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class
);
return http.build();
}
}
package com.example.app.security;
import com.example.app.service.UserDetailsServiceImpl;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider tokenProvider;
private final UserDetailsServiceImpl userDetailsService;
public JwtAuthenticationFilter(
JwtTokenProvider tokenProvider,
UserDetailsServiceImpl userDetailsService
) {
this.tokenProvider = tokenProvider;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
String username = tokenProvider.getUsernameFromJWT(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authentication.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
This implementation shows how to configure security in Spring Boot applications. The SecurityConfig class sets up JWT authentication, CORS, CSRF protection, and URL-based authorization. The JwtAuthenticationFilter handles token validation and user authentication. The implementation includes proper security headers and session management.
Exception Handling
Spring provides powerful exception handling mechanisms. Let’s implement comprehensive exception handling.
package com.example.app.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity handleResourceNotFoundException(
ResourceNotFoundException ex
) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage()
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity> handleValidationExceptions(
MethodArgumentNotValidException ex
) {
Map errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity handleGlobalException(Exception ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"An error occurred while processing your request"
);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@Data
@AllArgsConstructor
class ErrorResponse {
private int status;
private String message;
private long timestamp = System.currentTimeMillis();
}
This implementation shows how to handle exceptions in Spring Boot applications. The GlobalExceptionHandler provides centralized exception handling for different types of exceptions, including validation errors, resource not found errors, and general exceptions. The implementation includes proper error response formatting and HTTP status codes.
Testing
Spring Boot provides excellent testing support. Let’s implement comprehensive tests for our application.
package com.example.app.controller;
import com.example.app.dto.UserDTO;
import com.example.app.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Arrays;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private UserService userService;
@Test
void createUser_ShouldReturnCreatedUser() throws Exception {
UserDTO userDTO = new UserDTO();
userDTO.setUsername("testuser");
userDTO.setEmail("test@example.com");
userDTO.setPassword("password123");
given(userService.createUser(any(UserDTO.class))).willReturn(userDTO);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(userDTO)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.username").value(userDTO.getUsername()))
.andExpect(jsonPath("$.email").value(userDTO.getEmail()));
}
@Test
void getAllUsers_ShouldReturnUserList() throws Exception {
List users = Arrays.asList(
createUserDTO(1L, "user1", "user1@example.com"),
createUserDTO(2L, "user2", "user2@example.com")
);
given(userService.getAllUsers()).willReturn(users);
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].username").value("user1"))
.andExpect(jsonPath("$[1].username").value("user2"));
}
private UserDTO createUserDTO(Long id, String username, String email) {
UserDTO userDTO = new UserDTO();
userDTO.setId(id);
userDTO.setUsername(username);
userDTO.setEmail(email);
return userDTO;
}
}
package com.example.app.service;
import com.example.app.dto.UserDTO;
import com.example.app.model.User;
import com.example.app.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
@InjectMocks
private UserService userService;
@Test
void createUser_ShouldReturnSavedUser() {
UserDTO userDTO = new UserDTO();
userDTO.setUsername("testuser");
userDTO.setEmail("test@example.com");
userDTO.setPassword("password123");
User user = new User();
user.setUsername(userDTO.getUsername());
user.setEmail(userDTO.getEmail());
user.setPassword("encodedPassword");
given(userRepository.existsByUsername(any())).willReturn(false);
given(userRepository.existsByEmail(any())).willReturn(false);
given(passwordEncoder.encode(any())).willReturn("encodedPassword");
given(userRepository.save(any())).willReturn(user);
UserDTO result = userService.createUser(userDTO);
assertNotNull(result);
assertEquals(userDTO.getUsername(), result.getUsername());
assertEquals(userDTO.getEmail(), result.getEmail());
verify(userRepository).save(any(User.class));
}
}
This implementation shows how to write comprehensive tests for Spring Boot applications. The UserControllerTest demonstrates controller testing with MockMvc, including request/response validation. The UserServiceTest shows service layer testing with Mockito, including method invocation verification. Both test classes include proper setup and assertions.
Deployment and Monitoring
Spring Boot provides excellent deployment and monitoring capabilities. Let’s implement production-ready configuration.
# application-prod.yml
spring:
datasource:
url: ${SPRING_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_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: ${PORT:8080}
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,text/css,application/javascript,application/json
min-response-size: 1024
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
metrics:
enabled: true
logging:
level:
root: INFO
com.example.app: DEBUG
file:
name: /var/log/app/application.log
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
# Dockerfile
FROM eclipse-temurin:17-jdk-alpine as build
WORKDIR /workspace/app
COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src
RUN ./mvnw install -DskipTests
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
FROM eclipse-temurin:17-jre-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.app.Application"]
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/app
- SPRING_DATASOURCE_USERNAME=app
- SPRING_DATASOURCE_PASSWORD=app
- JWT_SECRET=your-secret-key
depends_on:
- db
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:14-alpine
environment:
- POSTGRES_DB=app
- POSTGRES_USER=app
- POSTGRES_PASSWORD=app
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
This implementation shows how to configure Spring Boot applications for production deployment. The application-prod.yml includes database configuration, security settings, server configuration, and logging setup. The Dockerfile demonstrates how to create a production-ready Docker image. The docker-compose.yml shows how to orchestrate the application with its dependencies.