Skip to main content

Spring & Spring Boot Detailed Cheatsheet

Table of Contents​

  1. Spring Core Concepts
  2. Dependency Injection & IoC
  3. Spring Annotations
  4. Spring Boot Fundamentals
  5. Spring Boot Annotations
  6. Configuration & Properties
  7. Spring MVC & REST APIs
  8. Data Access (JPA/Hibernate)
  9. Security
  10. Testing
  11. Actuator & Monitoring
  12. 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:​

@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;
}
}
@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
}