Spring & Spring Boot Detailed Cheatsheet
Table of Contents​
- Spring Core Concepts
- Dependency Injection & IoC
- Spring Annotations
- Spring Boot Fundamentals
- Spring Boot Annotations
- Configuration & Properties
- Spring MVC & REST APIs
- Data Access (JPA/Hibernate)
- Security
- Testing
- Actuator & Monitoring
- Best Practices
Spring Core Concepts​
What is Spring Framework?​
- Lightweight Java framework for enterprise applications
- Inversion of Control (IoC) and Dependency Injection (DI)
- Aspect-Oriented Programming (AOP)
- Modular architecture with various modules
Spring Modules:​
- Spring Core: IoC Container, Beans, Context
- Spring MVC: Web framework for building REST APIs and web apps
- Spring Data: Data access abstraction
- Spring Security: Authentication and authorization
- Spring Boot: Auto-configuration and convention over configuration
ApplicationContext vs BeanFactory:​
// BeanFactory - Lazy initialization
BeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
// ApplicationContext - Eager initialization (recommended)
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Dependency Injection & IoC​
Types of Dependency Injection:​
1. Constructor Injection (Recommended)​
@Component
public class OrderService {
private final PaymentService paymentService;
private final EmailService emailService;
// Constructor injection - ensures immutability
public OrderService(PaymentService paymentService, EmailService emailService) {
this.paymentService = paymentService;
this.emailService = emailService;
}
}
2. Setter Injection​
@Component
public class OrderService {
private PaymentService paymentService;
@Autowired
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
3. Field Injection (Not Recommended)​
@Component
public class OrderService {
@Autowired
private PaymentService paymentService; // Avoid this approach
}
Bean Scopes:​
@Component
@Scope("singleton") // Default - single instance per container
public class SingletonBean { }
@Component
@Scope("prototype") // New instance every time
public class PrototypeBean { }
@Component
@Scope("request") // Web - new instance per HTTP request
public class RequestBean { }
@Component
@Scope("session") // Web - new instance per HTTP session
public class SessionBean { }
Spring Annotations​
Core Annotations:​
// Component scanning
@Component // Generic stereotype
@Service // Business logic layer
@Repository // Data access layer
@Controller // Presentation layer (Spring MVC)
@RestController // @Controller + @ResponseBody
// Dependency Injection
@Autowired // Automatic dependency injection
@Qualifier("name") // Specify bean name when multiple candidates
@Primary // Preferred bean when multiple candidates
@Value("${prop}") // Inject property values
// Configuration
@Configuration // Indicates configuration class
@Bean // Method produces a bean
@ComponentScan // Enable component scanning
@Import // Import other configuration classes
Lifecycle Annotations:​
@Component
public class DatabaseConnection {
@PostConstruct // Called after dependency injection
public void init() {
// Initialize resources
}
@PreDestroy // Called before bean destruction
public void cleanup() {
// Clean up resources
}
}
Conditional Annotations:​
@ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true")
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProfile("production")
@Configuration
public class FeatureConfiguration { }
Spring Boot Fundamentals​
What is Spring Boot?​
- Opinionated framework built on top of Spring
- Auto-configuration based on classpath dependencies
- Embedded servers (Tomcat, Jetty, Undertow)
- Production-ready features out of the box
- Convention over configuration
Spring Boot Application Structure:​
src/
├── main/
│ ├── java/
│ │ └── com/example/myapp/
│ │ ├── MyApplication.java # Main class
│ │ ├── controller/ # REST controllers
│ │ ├── service/ # Business logic
│ │ ├── repository/ # Data access
│ │ ├── model/ # Entities/DTOs
│ │ └── config/ # Configuration classes
│ └── resources/
│ ├── application.properties # Configuration
│ ├── application.yml # YAML configuration
│ └── static/ # Static resources
└── test/ # Test classes
Main Application Class:​
@SpringBootApplication // Equivalent to @Configuration + @EnableAutoConfiguration + @ComponentScan
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Custom Configuration:​
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApplication.class);
app.setBannerMode(Banner.Mode.OFF); // Disable banner
app.setDefaultProperties(Collections.singletonMap("server.port", "8081"));
app.run(args);
}
}
Spring Boot Annotations​
Essential Spring Boot Annotations:​
// Application
@SpringBootApplication
@EnableAutoConfiguration
@SpringBootConfiguration
// Configuration
@ConfigurationProperties(prefix = "app")
@EnableConfigurationProperties(MyProperties.class)
// Web
@RestController
@RequestMapping("/api/v1")
@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping
// Validation
@Valid
@NotNull, @NotEmpty, @NotBlank
@Size(min = 1, max = 100)
@Email, @Pattern
// Testing
@SpringBootTest
@WebMvcTest
@DataJpaTest
@MockBean
@TestPropertySource
Configuration Properties Example:​
@ConfigurationProperties(prefix = "app")
@Component
public class AppProperties {
private String name;
private String version;
private Database database = new Database();
// getters and setters
public static class Database {
private String url;
private String username;
// getters and setters
}
}
Configuration & Properties​
Application Properties:​
# application.properties
# Server Configuration
server.port=8080
server.servlet.context-path=/api
# Database Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA/Hibernate
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
# Logging
logging.level.com.example=DEBUG
logging.level.org.springframework.web=DEBUG
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
# Actuator
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
YAML Configuration:​
# application.yml
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
jpa:
hibernate:
ddl-auto: update
show-sql: true
app:
name: My Application
version: 1.0.0
database:
url: ${spring.datasource.url}
username: ${spring.datasource.username}
Profile-Specific Configuration:​
# application.yml
spring:
profiles:
active: development
---
spring:
profiles: development
datasource:
url: jdbc:h2:mem:devdb
---
spring:
profiles: production
datasource:
url: jdbc:mysql://prod-server:3306/proddb
Spring MVC & REST APIs​
REST Controller Example:​
@RestController
@RequestMapping("/api/v1/users")
@Validated
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public ResponseEntity<List<UserDto>> getAllUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
List<UserDto> users = userService.getAllUsers(page, size);
return ResponseEntity.ok(users);
}
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUserById(@PathVariable Long id) {
UserDto user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
@PostMapping
public ResponseEntity<UserDto> createUser(@Valid @RequestBody CreateUserRequest request) {
UserDto user = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
@PutMapping("/{id}")
public ResponseEntity<UserDto> updateUser(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
UserDto user = userService.updateUser(id, request);
return ResponseEntity.ok(user);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}
Request/Response DTOs:​
// Request DTO
public class CreateUserRequest {
@NotBlank(message = "Name is required")
@Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")
private String name;
@Email(message = "Email should be valid")
@NotBlank(message = "Email is required")
private String email;
// getters and setters
}
// Response DTO
public class UserDto {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;
// getters and setters
}
Exception Handling:​
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
ErrorResponse error = new ErrorResponse("USER_NOT_FOUND", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationErrors(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
ErrorResponse error = new ErrorResponse("VALIDATION_ERROR", "Invalid input", errors);
return ResponseEntity.badRequest().body(error);
}
}
public class ErrorResponse {
private String code;
private String message;
private Map<String, String> details;
// constructors, getters, and setters
}
Data Access (JPA/Hibernate)​
Entity Definition:​
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false, length = 100)
private String name;
@Column(name = "email", unique = true, nullable = false)
private String email;
@CreationTimestamp
@Column(name = "created_at")
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
// constructors, getters, and setters
}
Repository Interface:​
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Query methods by naming convention
Optional<User> findByEmail(String email);
List<User> findByNameContaining(String name);
List<User> findByCreatedAtBetween(LocalDateTime start, LocalDateTime end);
// Custom queries
@Query("SELECT u FROM User u WHERE u.email = ?1")
Optional<User> findUserByEmail(String email);
@Query(value = "SELECT * FROM users WHERE name LIKE %?1%", nativeQuery = true)
List<User> findUsersByNameNative(String name);
@Modifying
@Query("UPDATE User u SET u.name = ?2 WHERE u.id = ?1")
int updateUserName(Long id, String name);
// Pagination and sorting
Page<User> findByNameContaining(String name, Pageable pageable);
}
Service Layer:​
@Service
@Transactional
public class UserService {
private final UserRepository userRepository;
private final ModelMapper modelMapper;
public UserService(UserRepository userRepository, ModelMapper modelMapper) {
this.userRepository = userRepository;
this.modelMapper = modelMapper;
}
@Transactional(readOnly = true)
public List<UserDto> getAllUsers(int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("name"));
Page<User> userPage = userRepository.findAll(pageable);
return userPage.getContent()
.stream()
.map(user -> modelMapper.map(user, UserDto.class))
.collect(Collectors.toList());
}
@Transactional(readOnly = true)
public UserDto getUserById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found with id: " + id));
return modelMapper.map(user, UserDto.class);
}
public UserDto createUser(CreateUserRequest request) {
User user = modelMapper.map(request, User.class);
User savedUser = userRepository.save(user);
return modelMapper.map(savedUser, UserDto.class);
}
}
Security​
Basic Security Configuration:​
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
private final UserDetailsService userDetailsService;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtRequestFilter jwtRequestFilter;
@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.csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
JWT Implementation:​
@Component
public class JwtUtil {
private final String jwtSecret = "mySecretKey";
private final int jwtExpiration = 86400; // 24 hours
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + jwtExpiration * 1000))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
Method-Level Security:​
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public UserDto getUserById(Long id) {
// Implementation
}
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
// Implementation
}
@PostAuthorize("hasRole('ADMIN') or returnObject.username == authentication.name")
public UserDto updateUser(Long id, UpdateUserRequest request) {
// Implementation
}
}
Testing​
Unit Testing:​
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private ModelMapper modelMapper;
@InjectMocks
private UserService userService;
@Test
void shouldReturnUserWhenValidId() {
// Given
Long userId = 1L;
User user = new User();
user.setId(userId);
user.setName("John Doe");
UserDto expectedDto = new UserDto();
expectedDto.setId(userId);
expectedDto.setName("John Doe");
when(userRepository.findById(userId)).thenReturn(Optional.of(user));
when(modelMapper.map(user, UserDto.class)).thenReturn(expectedDto);
// When
UserDto result = userService.getUserById(userId);
// Then
assertThat(result).isNotNull();
assertThat(result.getId()).isEqualTo(userId);
assertThat(result.getName()).isEqualTo("John Doe");
verify(userRepository).findById(userId);
verify(modelMapper).map(user, UserDto.class);
}
@Test
void shouldThrowExceptionWhenUserNotFound() {
// Given
Long userId = 1L;
when(userRepository.findById(userId)).thenReturn(Optional.empty());
// When & Then
assertThatThrownBy(() -> userService.getUserById(userId))
.isInstanceOf(UserNotFoundException.class)
.hasMessage("User not found with id: " + userId);
}
}
Integration Testing:​
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
class UserControllerIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private UserRepository userRepository;
@Test
void shouldCreateUserSuccessfully() {
// Given
CreateUserRequest request = new CreateUserRequest();
request.setName("John Doe");
request.setEmail("john@example.com");
// When
ResponseEntity<UserDto> response = restTemplate.postForEntity(
"/api/v1/users",
request,
UserDto.class
);
// Then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().getName()).isEqualTo("John Doe");
assertThat(response.getBody().getEmail()).isEqualTo("john@example.com");
// Verify in database
Optional<User> savedUser = userRepository.findByEmail("john@example.com");
assertThat(savedUser).isPresent();
assertThat(savedUser.get().getName()).isEqualTo("John Doe");
}
}
Web Layer Testing:​
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Autowired
private ObjectMapper objectMapper;
@Test
void shouldReturnUserWhenValidId() throws Exception {
// Given
Long userId = 1L;
UserDto userDto = new UserDto();
userDto.setId(userId);
userDto.setName("John Doe");
userDto.setEmail("john@example.com");
when(userService.getUserById(userId)).thenReturn(userDto);
// When & Then
mockMvc.perform(get("/api/v1/users/{id}", userId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(userId))
.andExpect(jsonPath("$.name").value("John Doe"))
.andExpect(jsonPath("$.email").value("john@example.com"));
verify(userService).getUserById(userId);
}
}
Actuator & Monitoring​
Actuator Configuration:​
# Enable actuator endpoints
management.endpoints.web.exposure.include=health,info,metrics,prometheus,loggers
management.endpoint.health.show-details=always
management.endpoint.health.show-components=always
# Custom info
info.app.name=My Spring Boot App
info.app.version=1.0.0
info.app.description=Sample Spring Boot Application
# Metrics
management.metrics.export.prometheus.enabled=true
management.metrics.distribution.percentiles-histogram.http.server.requests=true
Custom Health Indicator:​
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// Custom health check logic
boolean databaseUp = checkDatabaseConnection();
boolean externalServiceUp = checkExternalService();
if (databaseUp && externalServiceUp) {
return Health.up()
.withDetail("database", "Available")
.withDetail("externalService", "Available")
.build();
} else {
return Health.down()
.withDetail("database", databaseUp ? "Available" : "Unavailable")
.withDetail("externalService", externalServiceUp ? "Available" : "Unavailable")
.build();
}
}
private boolean checkDatabaseConnection() {
// Database connectivity check
return true;
}
private boolean checkExternalService() {
// External service check
return true;
}
}
Custom Metrics:​
@Service
public class UserService {
private final Counter userCreationCounter;
private final Timer userRetrievalTimer;
public UserService(MeterRegistry meterRegistry) {
this.userCreationCounter = Counter.builder("users.created")
.description("Number of users created")
.register(meterRegistry);
this.userRetrievalTimer = Timer.builder("users.retrieval.time")
.description("Time taken to retrieve user")
.register(meterRegistry);
}
public UserDto createUser(CreateUserRequest request) {
userCreationCounter.increment();
// User creation logic
}
public UserDto getUserById(Long id) {
return userRetrievalTimer.recordCallable(() -> {
// User retrieval logic
return userDto;
});
}
}
Best Practices​
1. Project Structure:​
com.example.myapp/
├── MyApplication.java # Main application class
├── config/ # Configuration classes
├── controller/ # REST controllers
├── service/ # Business logic
├── repository/ # Data access
├── model/
│ ├── entity/ # JPA entities
│ ├── dto/ # Data transfer objects
│ └── request/ # Request objects
├── exception/ # Custom exceptions
├── security/ # Security components
└── util/ # Utility classes
2. Configuration Management:​
// Use @ConfigurationProperties instead of @Value for grouped properties
@ConfigurationProperties(prefix = "app.database")
@Component
public class DatabaseProperties {
private String url;
private String username;
private String password;
private int maxConnections = 20;
// getters and setters
}
// Use profiles for environment-specific configuration
@Profile("production")
@Configuration
public class ProductionConfig {
// Production-specific beans
}
3. Exception Handling:​
// Create custom exceptions
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
// Use @ControllerAdvice for global exception handling
@ControllerAdvice
public class GlobalExceptionHandler {
// Exception handlers
}
4. Validation:​
// Use Bean Validation annotations
public class CreateUserRequest {
@NotBlank(message = "Name is required")
@Size(min = 2, max = 50)
private String name;
@Email(message = "Invalid email format")
@NotBlank(message = "Email is required")
private String email;
}
// Custom validation
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UniqueEmailValidator.class)
public @interface UniqueEmail {
String message() default "Email already exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
5. Logging:​
@Service
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public UserDto createUser(CreateUserRequest request) {
logger.info("Creating user with email: {}", request.getEmail());
try {
// User creation logic
logger.info("User created successfully with id: {}", user.getId());
return userDto;
} catch (Exception ex) {
logger.error("Failed to create user with email: {}", request.getEmail(), ex);
throw ex;
}
}
}
6. Performance Tips:​
// Use @Transactional appropriately
@Service
@Transactional
public class UserService {
@Transactional(readOnly = true) // For read operations
public UserDto getUserById(Long id) {
// Read operation
}
@Transactional(propagation = Propagation.REQUIRES_NEW) // For independent transactions
public void auditUserAction(String action) {
// Audit operation
}
}
// Use caching
@Service
public class UserService {
@Cacheable("users")
public UserDto getUserById(Long id) {
// This method result will be cached
}
@CacheEvict("users")
public void deleteUser(Long id) {
// This will evict the cache
}
}
// Enable caching in main class
@SpringBootApplication
@EnableCaching
public class MyApplication {
// Main method
}