Essentials Spring Boot Compendium
Module 1: Disambiguation - @Qualifier vs. @Primary
Section titled “Module 1: Disambiguation - @Qualifier vs. @Primary”The Concept: Both annotations solve the problem of ambiguity when the Spring IoC container finds multiple beans of the same type that could satisfy a dependency.
The Problem It Solves:
Imagine you have an interface NotificationService and two implementations: EmailService and SmsService.
public interface NotificationService { void send(String message); }@Component public class EmailService implements NotificationService { ... }@Component public class SmsService implements NotificationService { ... }When another bean needs a NotificationService, Spring throws a NoUniqueBeanDefinitionException because it doesn’t know which one to inject.
The Mechanisms & Production Use Cases:
-
@Primary: The “Default” Choice- How it works: You place
@Primaryon one of the bean definitions. This tells Spring, “If you are ever confused and have multiple options, use this one as the default.” - Use Case: This is perfect when you have a common, standard implementation and one or more specialized alternatives. For example, you might have a primary
dataSourcebean that connects to your main production database, but anothertestDataSourcebean for integration tests. The main one would be marked@Primary.
@Bean@Primarypublic DataSource primaryDataSource() { /* ... connects to PostgreSQL ... */ }@Beanpublic DataSource testDataSource() { /* ... connects to in-memory H2 ... */ } - How it works: You place
-
@Qualifier: The “Specific” Choice- How it works: You give each implementation a unique name (
@Component("emailNotifier")) and then use@Qualifierat the injection point to specify exactly which bean you want. - Use Case: This is the better choice when there is no clear default and the selection is context-dependent. A user profile service might need to inject both notification types to send different kinds of alerts.
// The implementations@Component("emailNotifier")public class EmailService implements NotificationService { ... }@Component("smsNotifier")public class SmsService implements NotificationService { ... }// The consumer@Servicepublic class UserProfileService {private final NotificationService emailService;private final NotificationService smsService;// Constructor injection specifying which bean to usepublic UserProfileService(@Qualifier("emailNotifier") NotificationService emailService,@Qualifier("smsNotifier") NotificationService smsService) {this.emailService = emailService;this.smsService = smsService;}} - How it works: You give each implementation a unique name (
Interview Gold (Q&A):
- Q: “When would you choose
@Primaryover@Qualifier?” - A: “I choose
@Primarywhen there is a clear, application-wide default implementation. It follows the principle of ‘convention over configuration’ and keeps the injection points clean. I choose@Qualifierwhen there are multiple, equally valid implementations and the choice is specific to the consumer bean. It makes the dependency explicit and is clearer in complex scenarios where different parts of the application need different implementations of the same interface.”
Module 2: Configuration - properties vs. yaml
Section titled “Module 2: Configuration - properties vs. yaml”The Concept: Both are file formats for externalizing application configuration.
Beyond the Superficial:
| Feature | application.properties | application.yml |
|---|---|---|
| Syntax | Flat key-value pairs using dot notation. | Hierarchical, using indentation. |
| Example | server.port=8080server.servlet.context-path=/api | server:port: 8080servlet:context-path: /api |
| Pros | Simpler syntax, standard in Java, easy for scripts to parse. | More readable for complex/nested configurations, avoids repetition, supports lists and multi-line strings easily. |
| Cons | Can become very repetitive and hard to read with deep nesting. | Strict indentation rules can lead to parsing errors, slightly less common in pure Java ecosystems. |
Best Practices & Interview Nuance:
- The professional answer is not “always use YAML.” The professional answer is “consistency is key.”
- Use YAML for human-managed, complex configurations where the hierarchy provides clarity (like defining multiple data sources or security filter chains).
- Use
.propertiesfor simpler applications, or when configuration values are often managed or generated by automated scripts where the simple key-value format is more robust. - Hybrid Approach: It’s valid to use both. You can have a base
application.ymland import profile-specific properties from a.propertiesfile if needed.
Module 3: Environment Flexibility - Relaxed Binding
Section titled “Module 3: Environment Flexibility - Relaxed Binding”The Concept: Spring Boot is highly flexible in how it maps configuration properties from various sources to the fields in your @ConfigurationProperties beans.
The Mechanism:
Spring normalizes all property keys. This means the following formats in a configuration file are all considered equivalent and will bind to a myApiKey field:
| Format | Example | Common Use Case |
|---|---|---|
| kebab-case | app.my-api-key=value | Recommended in YAML/properties files. |
| camelCase | app.myApiKey=value | Alternative in files. |
| snake_case | app.my_api_key=value | Alternative, less common. |
| UPPER_SNAKE | APP_MY_API_KEY=value | Crucial for Environment Variables. |
Why It’s a Production Superpower:
This allows for seamless overriding across different environments. A developer can define a property in application.yml using readable kebab-case: my-app.api-key: dev-key. In production, the operations team can override this value by setting a standard environment variable in a Docker or Kubernetes environment: MY_APP_API_KEY=prod-secret-key. No code change is needed. The application just works. This flexibility is a core tenet of cloud-native application design.
Module 4: Environment Separation - Spring Boot Profiles
Section titled “Module 4: Environment Separation - Spring Boot Profiles”The Concept: Profiles are a core Spring feature that allows you to register different beans and configuration for different environments (e.g., dev, test, prod).
Activation Mechanisms:
- In
application.properties/yml:spring.profiles.active=dev - Command-line argument (overrides file):
java -jar my-app.jar --spring.profiles.active=prod - Environment Variable (overrides all):
SPRING_PROFILES_ACTIVE=prod
Key Use Cases:
-
Configuration Files: Create profile-specific property files like
application-prod.ymlorapplication-dev.yml. These properties will override the baseapplication.ymlwhen that profile is active. -
@ProfileAnnotation: Conditionally register a bean only when a specific profile is active. This is perfect for defining environment-specific implementations.// This bean will ONLY be created if the 'dev' or 'test' profile is active@Component@Profile({"dev", "test"})public class MockEmailService implements EmailService { ... }// This bean will ONLY be created if the 'prod' profile is active@Component@Profile("prod")public class RealSmtpEmailService implements EmailService { ... } -
Multi-profile Setup: You can activate multiple profiles (e.g.,
--spring.profiles.active=prod,cloud). In case of conflicting properties, the last profile listed wins.
Module 5: Operations - Spring Boot Actuator
Section titled “Module 5: Operations - Spring Boot Actuator”The Concept: A production-ready feature set that exposes operational information about your running application via HTTP endpoints or JMX. It’s enabled by adding the spring-boot-starter-actuator dependency.
Key Endpoints (must-know):
/actuator/health: Provides a summary of the application’s health. It aggregates the status of multiple components (database, disk space, message queues). ADOWNstatus here is a critical alert for monitoring systems./actuator/info: Displays arbitrary application information. Useful for exposing the Git commit hash, build version, etc., so you know exactly what code is running in an environment./actuator/metrics: Provides detailed metrics (CPU usage, JVM memory, HTTP request latencies, etc.) that can be scraped by monitoring systems like Prometheus./actuator/env: Shows all environment properties and their sources. Invaluable for debugging configuration issues.
Custom Health Checks:
For a senior-level answer, you must know how to extend this. You can create your own HealthIndicator bean to check the status of a critical downstream service.
@Componentpublic class PaymentGatewayHealthIndicator implements HealthIndicator { @Override public Health health() { if (isPaymentGatewayUp()) { return Health.up().withDetail("service", "Payment Gateway is reachable").build(); } // This will cause the main /actuator/health endpoint to report DOWN. return Health.down().withDetail("service", "Payment Gateway is NOT reachable").build(); } // ... logic to ping the payment gateway ...}Module 6: Web Layer - Interceptors vs. Filters
Section titled “Module 6: Web Layer - Interceptors vs. Filters”This is a classic interview question that tests your understanding of the servlet architecture vs. Spring’s dispatching.
| Feature | Filter (Servlet API) | HandlerInterceptor (Spring MVC) |
|---|---|---|
| Scope | Servlet Container Level. It operates on the raw HttpServletRequest and Response. It runs before the DispatcherServlet even gets the request. | Spring MVC Level. It operates within Spring’s context, after the DispatcherServlet has received the request. |
| Awareness | Not Spring-aware. It has no access to the Spring ApplicationContext or beans by default. | Fully Spring-aware. It can be injected with any Spring bean. |
| Context | Has no knowledge of which Spring controller or method will eventually handle the request. | Has access to the HandlerMethod object, so it knows exactly which controller and method will be invoked. |
| Use Case | Cross-cutting concerns that are framework-agnostic: raw request logging, GZip compression, CORS handling, some types of authentication before Spring Security. | Cross-cutting concerns that require Spring context: detailed authorization checks (e.g., checking a user’s role against the handler method’s permissions), adding common attributes to the ModelAndView, pre- and post-processing. |
Module 7: Startup Logic - CommandLineRunner vs. ApplicationRunner
Section titled “Module 7: Startup Logic - CommandLineRunner vs. ApplicationRunner”The Concept: Both are interfaces that allow you to execute code after the Spring ApplicationContext has been fully initialized but before the application starts accepting requests.
The Difference:
CommandLineRunner: Therunmethod provides access to the command-line arguments as a simple string array (String... args).ApplicationRunner: Therunmethod provides access to a parsedApplicationArgumentsobject, which offers a richer API for accessing arguments, including option flags (--my-flag=value) and non-option arguments.
When to use:
Use them for one-time initialization tasks like seeding a database, warming up a cache, or scheduling initial background jobs. ApplicationRunner is generally preferred as it provides a more robust way to handle complex command-line arguments.
Module 8: High-Level Concepts (Awareness Check)
Section titled “Module 8: High-Level Concepts (Awareness Check)”-
HATEOAS (Hypermedia as the Engine of Application State): The concept that a REST API response should include links to related actions or resources. For example, an “order” response might contain a “cancel” link if the order is still in a cancellable state. It allows clients to navigate the API dynamically. You just need to know the concept.
-
Banner Customization: Spring Boot prints a banner on startup. You can customize this by adding a
banner.txtfile to yoursrc/main/resources. It’s a fun feature for team branding. -
Embedded Servers (Tomcat/Jetty/Undertow): Spring Boot applications are self-contained. They bundle a servlet container (Tomcat by default) directly into the executable JAR file. This eliminates the need for traditional deployment to an external web server, which is a cornerstone of microservice architecture.
-
Servlet (MVC) vs. Reactive (WebFlux):
- Spring MVC (Servlet): The traditional model. It uses a thread-per-request model. It is blocking by nature. It’s simpler to reason about and excellent for most standard CRUD applications and CPU-bound tasks.
- Spring WebFlux (Reactive): A newer model. It uses an event-loop with a small number of threads to handle many concurrent requests. It is non-blocking. It excels in I/O-bound applications (like API gateways or services with many slow downstream calls) and streaming scenarios.
Module 9: Essential Annotations (Quick Reference)
Section titled “Module 9: Essential Annotations (Quick Reference)”-
@PropertySource:- What: An annotation to load properties from a specific file (other than the default
application.properties). - Example:
@PropertySource("classpath:custom-config.properties")placed on a@Configurationclass.
- What: An annotation to load properties from a specific file (other than the default
-
@PostConstructand@PreDestroy(JSR-250 Annotations):- What: Bean lifecycle callbacks.
@PostConstruct: Marks a method to be executed after a bean has been constructed and all its dependencies have been injected. Perfect for initialization logic that requires those dependencies.@PreDestroy: Marks a method to be executed just before the bean is removed from the application context during shutdown. Ideal for cleanup tasks like closing network connections or releasing resources.- Example:
@Componentpublic class MyResourceConnection {@PostConstructpublic void init() {// Open connection after dependencies are set}@PreDestroypublic void cleanup() {// Close connection gracefully on shutdown}}