Here’s a complete and in-depth explanation of Spring Boot:
Spring Boot is a Java-based framework that simplifies the process of developing Spring applications, especially web applications and microservices. It makes it easier to get started with Spring by providing pre-configured dependencies, auto-configuration, and other features that streamline development. Spring Boot also allows for creating stand-alone applications with minimal configuration, making it easier to deploy and manage.
Spring Boot reduces the amount of configuration needed for Spring applications, allowing developers to focus on the application's logic rather than infrastructure details.
Spring Boot automatically configures many aspects of a Spring application, such as the web server (Tomcat, Jetty, Undertow) and data access libraries, based on the dependencies included in the project.
Spring Boot enables the creation of stand-alone, executable applications that can be run using java -jar
or traditional WAR deployments.
Spring Boot provides sensible defaults for various Spring features, allowing developers to get started quickly and only customize the application as needed.
Spring Boot allows embedding servers like Tomcat, Jetty, or Undertow directly into the application, eliminating the need for external server deployments.
Spring Boot includes features like metrics, health checks, and externalized configuration, which are useful for building production-ready applications.
Spring Boot generally does not require XML configuration or code generation, making it a more modern and efficient approach to Spring development.
Feature | Spring Framework | Spring Boot |
---|---|---|
Definition | A lightweight Java framework for building enterprise applications. | An extension of Spring that simplifies setup, configuration, and deployment. |
Setup | Requires manual configuration of dependencies, XML/Java config, servlet setup, etc. | Auto-configures everything; embedded servers like Tomcat/Jetty; minimal setup. |
Boilerplate | Needs a lot of boilerplate code. | Eliminates boilerplate code. |
Deployment | Requires deploying WAR to external servers. | Can be run as a standalone JAR. |
Focus | Flexibility and full control. | Convention over configuration. |
Use Case | Best for complex, fine-tuned enterprise apps. | Best for microservices and rapid application development. |
You don’t need to write lengthy configuration.
Automatically sets up things (e.g., database, MVC, REST, security).
Embedded servers like Tomcat so no need to deploy WARs.
Easier to create microservices.
Faster prototyping and production-ready apps.
Auto-Configuration (@EnableAutoConfiguration
):
Analyzes your classpath and auto-configures beans based on available libraries.
Example: If spring-boot-starter-web
is added, it sets up Spring MVC, Jackson, embedded Tomcat, etc.
Spring Boot Starters:
Predefined dependency sets for specific features.
E.g., spring-boot-starter-data-jpa
, spring-boot-starter-security
.
Spring Boot Actuator:
Provides monitoring & metrics (e.g., health, logs, beans).
CommandLineRunner & ApplicationRunner:
Hooks to run custom logic after Spring context initializes.
Application.properties / application.yml:
Centralized configuration without external files.
Embedded Server:
Runs directly using java -jar yourapp.jar
.
Auto Configuration
Microservices Ready
Embedded Servers
Faster Development
Production-Ready Metrics with Actuator
Spring Ecosystem Integration (JPA, Security, Cloud, Kafka)
Microservices architecture
RESTful APIs
Web applications (admin portals, dashboards)
Backend for mobile/web games
Internal tools and CRMs
Billing or Payment systems
Cloud-native apps (AWS, GCP, Azure)
IoT applications
Feature | Dependency Injection (DI) | Dependency Management (DM) |
---|---|---|
Focus | Managing object creation and wiring dependencies. | Managing library versions and transitive dependencies. |
Spring Role | Core principle; uses @Autowired , @Component , etc. | Handled via Maven/Gradle using spring-boot-starter-* |
Analogy | Plug-and-play of components. | Handling which tools you need for your code. |
Type | Description | Example |
---|---|---|
Constructor Injection | Dependency passed via constructor. | public PlayerService(GameEngine engine) |
Setter Injection | Dependency passed via setter method. | setGameEngine(GameEngine engine) |
Field Injection | Uses @Autowired directly on fields. | @Autowired GameEngine engine; |
When two or more beans depend on each other in a way that creates a loop.
class A {
@Autowired
B b;
}
class B {
@Autowired
A a;
}
Spring fails with CircularDependencyException
unless you break the loop using @Lazy
, @PostConstruct
, or refactor.
Feature | Setter | Constructor |
---|---|---|
Optional Dependencies | Good for optional | Constructor fails if missing |
Testability | Not good, harder to test | Better for testing |
Immutability | Mutable | Immutable |
Circular Dependency Risk | More tolerant | Safer; detects early |
Let’s design a simple backend for an online multiplayer RPG game:
GameEngine
: Core class that controls gameplay.
PlayerService
: Manages player profiles and states.
WeaponService
: Manages player weapons.
InventoryService
: Manages player inventory.
MatchService
: Handles match sessions between players.
@Service
public class PlayerService {
private final GameEngine engine;
private final WeaponService weaponService;
public PlayerService(GameEngine engine, WeaponService weaponService) {
this.engine = engine;
this.weaponService = weaponService;
}
}
What if WeaponService
also needs PlayerService
?
@Service
public class WeaponService {
private final PlayerService playerService;
public WeaponService(@Lazy PlayerService playerService) {
this.playerService = playerService;
}
}
⚠️ This is Circular Dependency, so @Lazy
delays creation to break the loop.
Add spring-boot-starter-web
, spring-boot-starter-data-jpa
.
Auto-config sets up MVC, REST Controllers.
PlayerController
handles API calls.
DI injects PlayerService
into PlayerController
.
PlayerService
calls WeaponService
, InventoryService
.
Repositories auto-wired via Spring Data JPA.
Application runs in embedded Tomcat using java -jar
.
Imagine we are building a Multiplayer Game Backend in Spring Boot with:
GameEngine
: Core game loop logic
PlayerService
: Manages player profiles
WeaponService
: Handles weapons
InventoryService
: Manages items
MatchService
: Starts/ends PvP battles
Mandatory dependencies
Immutability
Testability
Avoiding circular dependencies
@Service
public class MatchService {
private final GameEngine gameEngine;
private final PlayerService playerService;
public MatchService(GameEngine gameEngine, PlayerService playerService) {
this.gameEngine = gameEngine;
this.playerService = playerService;
}
public void startMatch(String playerId) {
// Uses playerService & gameEngine to start match
}
}
Match must have GameEngine
and PlayerService
— no match can start otherwise.
Enforces correct construction at compile time.
Easy to test with mocks.
Optional dependencies
Configurable or replaceable logic
Circular dependency workaround (not preferred, but used)
📦 Example Use Case in Gaming:
@Service
public class InventoryService {
private WeaponService weaponService;
@Autowired
public void setWeaponService(WeaponService weaponService) {
this.weaponService = weaponService;
}
public void showWeapons(String playerId) {
// Optional: weaponService may be null for basic players
}
}
Weapon system may be disabled in demo mode, so InventoryService
can work without it.
Use in dev/test environments where weapons are not initialized.
Fast prototyping
When testability or immutability are not concerns
@Service
public class NotificationService {
@Autowired
private PlayerService playerService;
public void notifyPlayer(String playerId, String message) {
playerService.sendMessage(playerId, message);
}
}
Harder to test (no constructor to inject mocks)
No clear visibility of dependencies
Cannot mark as final
— leads to mutable objects
Hides dependencies — makes code harder to maintain
Injection Type | Use When | Gaming Example | Pros | Cons |
---|---|---|---|---|
Constructor | Mandatory dependency | MatchService , GameEngine | Immutable, testable | Breaks with circular dependencies |
Setter | Optional dependency | InventoryService uses WeaponService optionally | Flexible, supports circular | Verbose, less safe |
Field | Fast dev, low priority | NotificationService uses PlayerService | Clean, fast | Poor testability, hidden deps |
@SpringBootApplication
Shortcut for:
@Configuration
@EnableAutoConfiguration
@ComponentScan
@SpringBootApplication
public class BankApp {
public static void main(String[] args) {
SpringApplication.run(BankApp.class, args);
}
}
Bootstraps your whole application — like starting a Banking Core System server.
@ComponentScan
Scans specified packages for Spring beans (@Component
, @Service
, @Controller
, etc.)
@SpringBootApplication
@ComponentScan(basePackages = "com.bank.loan")
public class BankApp { ... }
If LoanService
is in a different package (com.bank.loan
), you tell Spring to look there.
@EnableAutoConfiguration
Automatically configures beans based on dependencies (like DataSource, Web, etc.)
@EnableAutoConfiguration
public class BankAppConfig { ... }
@SpringBootConfiguration
Same as @Configuration
, but specific to Spring Boot.
When you want a class to be the root configuration, but not as the main class.
@SpringBootConfiguration
public class BankAppConfig {
// Bean definitions
}
@RestController
vs @Controller
Annotation | Used for | Returns | Example |
---|---|---|---|
@RestController | REST APIs | JSON/XML | /api/account/balance |
@Controller | MVC/Web Pages | HTML (view name) | /login , /dashboard |
@RestController
@RequestMapping("/api/account")
public class AccountRestController {
@GetMapping("/balance")
public double getBalance() {
return 10500.75;
}
}
@Controller
public class AccountWebController {
@GetMapping("/dashboard")
public String showDashboard() {
return "dashboard"; // returns dashboard.html
}
}
@PathVariable
Used to extract values from the URL path.
@GetMapping("/account/{accNo}")
public String getAccount(@PathVariable String accNo) {
return "Details for Account: " + accNo;
}
URL → /account/123456
→ accNo = 123456
@RequestParam
Used to extract query parameters from the URL.
@GetMapping("/loan/search")
public String searchLoan(@RequestParam String type, @RequestParam int duration) {
return "Searching for " + type + " loans for " + duration + " years";
}
@RequestBody
Used to accept full JSON body in POST
or PUT
.
@PostMapping("/account")
public String createAccount(@RequestBody Account account) {
return "Account created for " + account.getName();
}
@RequestHeader
Extract values from the header of the HTTP request.
@GetMapping("/validate")
public String validate(@RequestHeader("auth-token") String token) {
return "Token received: " + token;
}
Annotation | Extracts From | Example URL / Use |
---|---|---|
@PathVariable | URL path | /account/123 |
@RequestParam | Query string | /loan?type=home |
@RequestBody | JSON body | POST /account |
@RequestHeader | Headers | auth-token |
If you don't want Spring Boot to auto-configure a database (like H2, DataSource, or JPA):
exclude
in @SpringBootApplication
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class
})
public class BankApp { ... }
🧠 Used in Microservices that don’t use DB, or use custom DB clients (MongoTemplate, JDBC manually, etc.)
🔹 Way 2: Using application.properties
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
Spring Boot uses embedded Tomcat by default.
Remove Tomcat dependency (if declared):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
Add Jetty dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
You can similarly use Undertow if you prefer lightweight, fast startups for reactive systems.
main()
methodpublic class MyBankApp {
public static void main(String[] args) {
SpringApplication.run(MyBankApp.class, args);
}
}
Creates SpringApplication
instance
Loads ApplicationContext
Scans & Instantiates Beans
Starts Embedded Server (Tomcat/Jetty)
Attaches Application Listeners
Registers Shutdown Hooks
Main thread becomes non-daemon to keep the app running
main()
The thread you see in main()
is non-daemon
Keeps the application alive
Starts embedded server thread (Tomcat/Jetty/Undertow)
Spring Boot starts many daemon threads, including:
background-preinitializer
Tomcat-Acceptor-*
Tomcat-Nio-*
Scheduling threads
(for @Scheduled
)
AsyncExecutor threads
(if enabled)
Main Thread = Launches the game engine.
Game Loop Thread (non-daemon) = Keeps the game running.
Matchmaking Thread (daemon) = Runs in background.
Shutdown Hook Thread = Cleans up resources when server is stopped.
@Component
public class StartupTask implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("Banking server booted at " + LocalDateTime.now());
}
}
Or:
@Bean
public ApplicationRunner customRunner() {
return args -> {
System.out.println("Executed after Spring Context ready.");
};
}
If you want to use Spring Boot without web (no server):
public static void main(String[] args) {
new SpringApplicationBuilder(MyBankApp.class)
.web(WebApplicationType.NONE)
.run(args);
}
Useful for:
CLI tools
Batch jobs
Microservices without HTTP layer
Let's build a Spring Boot + H2 in-memory POC for a Bank Statement Feature, where a user can:
Fetch transactions for the current month
Fetch transactions by a specific month (e.g., March 2025)
Fetch transactions between a date range
Spring Boot
H2 In-Memory DB
Spring Data JPA
REST API
🔖 ENTITY: Transaction
@Entity
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String accountNumber;
private String description;
private Double amount;
private LocalDate transactionDate;
private String transactionType; // CREDIT / DEBIT
// Getters, Setters, Constructors
}
📦 REPOSITORY: TransactionRepository
@Repository
public interface TransactionRepository extends JpaRepository<Transaction, Long> {
// 1. Transactions for current month
@Query("SELECT t FROM Transaction t WHERE MONTH(t.transactionDate) = MONTH(CURRENT_DATE) AND YEAR(t.transactionDate) = YEAR(CURRENT_DATE)")
List<Transaction> findCurrentMonthTransactions();
// 2. Transactions for a specific month & year
@Query("SELECT t FROM Transaction t WHERE MONTH(t.transactionDate) = :month AND YEAR(t.transactionDate) = :year")
List<Transaction> findByMonthAndYear(@Param("month") int month, @Param("year") int year);
// 3. Transactions between date range
List<Transaction> findByTransactionDateBetween(LocalDate from, LocalDate to);
}
⚙️ SERVICE: TransactionService
@Service
public class TransactionService {
@Autowired
private TransactionRepository transactionRepository;
public List<Transaction> getCurrentMonthTransactions() {
return transactionRepository.findCurrentMonthTransactions();
}
public List<Transaction> getByMonthAndYear(int month, int year) {
return transactionRepository.findByMonthAndYear(month, year);
}
public List<Transaction> getByDateRange(LocalDate start, LocalDate end) {
return transactionRepository.findByTransactionDateBetween(start, end);
}
}
🌐 CONTROLLER: TransactionController
@RestController
@RequestMapping("/api/transactions")
public class TransactionController {
@Autowired
private TransactionService transactionService;
// 1. Current month transactions
@GetMapping("/current")
public List<Transaction> getCurrentMonthTransactions() {
return transactionService.getCurrentMonthTransactions();
}
// 2. By specific month/year
@GetMapping("/month")
public List<Transaction> getByMonthYear(
@RequestParam int month,
@RequestParam int year) {
return transactionService.getByMonthAndYear(month, year);
}
// 3. By date range
@GetMapping("/range")
public List<Transaction> getByDateRange(
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to) {
return transactionService.getByDateRange(from, to);
}
}
Current month
GET /api/transactions/current
By month (e.g., March 2025)
GET /api/transactions/month?month=3&year=2025
By range:
GET /api/transactions/range?from=2025-03-01&to=2025-03-31
🛠 application.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create
spring.h2.console.path=/h2-console
🧪 Insert Test Data on Startup
@Component
public class DataSeeder implements CommandLineRunner {
@Autowired
private TransactionRepository repo;
@Override
public void run(String... args) throws Exception {
repo.save(new Transaction(null, "123456", "Salary", 50000.0, LocalDate.now().withDayOfMonth(5), "CREDIT"));
repo.save(new Transaction(null, "123456", "Grocery", -5000.0, LocalDate.now().withDayOfMonth(10), "DEBIT"));
repo.save(new Transaction(null, "123456", "Rent", -15000.0, LocalDate.of(2025, 3, 1), "DEBIT"));
repo.save(new Transaction(null, "123456", "Gift", 2000.0, LocalDate.of(2025, 2, 25), "CREDIT"));
}
}
Next →
Auto-Configuration in Spring Boot means that Spring Boot attempts to automatically configure your Spring application based on the dependencies and classes available on your classpath.
Instead of you manually defining beans and settings (like database datasource, MVC config, Jackson JSON, etc.),
Spring Boot tries to guess and create those beans for you if you have certain libraries in your project.
Speeds up development
Removes boilerplate XML or Java config
Uses sensible defaults, but can be customized
Makes Spring “opinionated” but flexible
Enable Auto-Configuration:
When you use @SpringBootApplication
, it includes @EnableAutoConfiguration
which triggers Spring Boot auto-config.
Look for Auto-Configuration Classes:
Spring Boot scans META-INF/spring.factories files from all JAR dependencies.
It looks for all classes listed under the key:org.springframework.boot.autoconfigure.EnableAutoConfiguration
Evaluate Conditions:
Each auto-config class is annotated with conditional annotations like:
@ConditionalOnClass
(load only if a certain class is on the classpath)
@ConditionalOnBean
(only if some bean is already defined)
@ConditionalOnMissingBean
(only if no other bean of the same type exists)
@ConditionalOnProperty
(enable only if specific properties are set)
Apply Configurations:
If all conditions pass, the auto-config class creates beans and configures the context.
spring/org.springframework.boot.autoconfigure
spring.factories
?It's a file located under META-INF/
inside Spring Boot JARs and your dependencies.
It lists fully qualified class names of auto-configuration classes that Spring Boot should consider.
spring.factories
entry:# org.springframework.boot.autoconfigure.EnableAutoConfiguration= lists classes for auto-config
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
Spring Boot's auto-configuration loader reads these entries and tries to load all these classes automatically.
Each class has bean definitions & conditional logic.
When you add spring-boot-starter-jdbc or spring-boot-starter-data-jpa,
Spring Boot sees that javax.sql.DataSource
class is present.
It loads DataSourceAutoConfiguration
from the spring.factories
.
This configures a DataSource
bean if no other DataSource
is defined by you.
It uses properties like spring.datasource.url
, username
, etc. from your application.properties
.
You can customize or disable auto-configuration:
Exclude specific auto-config classes
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class MyApp { ... }
Define your own beans
Spring Boot will back off if it finds user-defined beans of the same type (thanks to @ConditionalOnMissingBean
).
Concept | Description |
---|---|
Auto-Configuration | Spring Boot auto-magically configures beans based on classpath & properties. |
@EnableAutoConfiguration | Activates auto-configuration support |
spring.factories | File listing auto-configuration classes to load |
Conditional Annotations | Conditions on class presence, bean presence, properties, etc. to selectively apply auto-config |
Customization | Override by excluding auto-config classes or defining your own beans |
You build reusable libraries/starter projects that automatically configure themselves when added as a dependency.
Makes your library plug-and-play with zero or minimal setup for users.
Mimics how Spring Boot starter modules work (spring-boot-starter-web
, spring-boot-starter-data-jpa
, etc.).
Create a new project (can be a separate maven module or jar) that will hold your auto-configuration.
Create a Java config class with @Configuration
. Annotate it with @ConditionalOnClass
, @ConditionalOnMissingBean
, or other conditional annotations to make it smart.
Example: Suppose you want to auto-configure a simple service bean MyService
only if the class com.example.service.MyService
is on the classpath.
package com.example.autoconfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@Configuration
@ConditionalOnClass(name = "com.example.service.MyService")
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService() {
return new MyService();
}
}
spring.factories
fileCreate the file in your resources folder:
src/main/resources/META-INF/spring.factories
Add this content:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfig.MyServiceAutoConfiguration
Package your module as a jar.
Add it as a dependency in any Spring Boot application.
When Spring Boot starts, it will load your auto-configuration class automatically, creating MyService
bean if the conditions are met.
Create a test Spring Boot app, add your jar, and check if the bean is injected automatically.
You can add @ConditionalOnProperty
to enable/disable your auto-config via properties.
@ConditionalOnProperty(name = "my.service.enabled", havingValue = "true", matchIfMissing = true)
This way, users can control your auto-config by adding:
my.service.enabled=false
Next →
Starters are convenient dependency descriptors provided by Spring Boot.
They bundle a set of related dependencies you usually need for a certain functionality into one single dependency.
Why Starters?
Avoid manual dependency management
Easily add complete features (web, JPA, security, etc.)
Ensure compatible versions of dependencies
Starter Dependency | Purpose |
---|---|
spring-boot-starter-web | Build RESTful web applications with Spring MVC and embedded Tomcat |
spring-boot-starter-data-jpa | Spring Data JPA with Hibernate for DB access |
spring-boot-starter-security | Adds Spring Security for authentication/authorization |
spring-boot-starter-test | Testing libraries (JUnit, Mockito, etc.) |
Example: Adding spring-boot-starter-web
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
This adds everything to build REST controllers, handle JSON (Jackson), and embed Tomcat server.
Next →
Steps to Create a Custom Spring Boot Starter in Java
This project will be your starter module, e.g. mycompany-spring-boot-starter-foo
.
In your starter’s pom.xml
or build.gradle
, add dependencies that you want to expose to users.
Mark some dependencies as compile
or implementation
(to be transitive).
Add spring-boot-autoconfigure
as a dependency with provided
or normal scope because you will create auto-config classes.
Example in Maven pom.xml
:
<dependencies>
<!-- The library/dependencies you want to bundle -->
<dependency>
<groupId>com.example</groupId>
<artifactId>foo-library</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Required for auto-configuration -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring.boot.version}</version>
</dependency>
</dependencies>
Create a class annotated with @Configuration
and @ConditionalOnXXX
annotations (like @ConditionalOnClass
, etc.) to activate your config only if dependencies are present.
Use @AutoConfigureAfter
or @AutoConfigureBefore
if needed.
Use @EnableConfigurationProperties
if you want to bind properties.
Example:
package com.example.foo.autoconfigure;
import com.example.foo.FooService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass(FooService.class) // Enable only if FooService is on classpath
public class FooAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public FooService fooService() {
return new FooService();
}
}
spring.factories
Create a src/main/resources/META-INF/spring.factories
file.
Register your auto-config class under key:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.foo.autoconfigure.FooAutoConfiguration
spring-configuration-metadata.json
To allow IDE support for your starter’s configuration properties.
Use @ConfigurationProperties
and generate metadata via spring-boot-configuration-processor
.
mvn clean install
Just add your starter dependency:
<dependency>
<groupId>com.example</groupId>
<artifactId>mycompany-spring-boot-starter-foo</artifactId>
<version>1.0.0</version>
</dependency>
When your app runs, Spring Boot will pick your auto-config and configure your beans.
Next →
A command-line tool to quickly create, run, and test Spring Boot apps without the full IDE.
Supports running apps written in Groovy or Java.
Download from Spring’s site or install via SDKMAN or Homebrew (macOS).
sdk install springboot
# or
brew install springboot
The main way to externalize configuration.
Can use .properties
or .yml
format.
Example: application.properties
game.leaderboard.max-players=100
game.leaderboard.timezone=UTC
For different environments (dev, test, prod), use profiles.
Create files like application-dev.properties
, application-prod.yml
.
Activate profile with -Dspring.profiles.active=dev
Profiles can override default properties.
Useful to switch DB URL, logging level, etc. per environment.
@Value
and @ConfigurationProperties
@Value
@Component
public class GameSettings {
@Value("${game.leaderboard.max-players}")
private int maxPlayers;
}
@ConfigurationProperties
Better for grouping related props.
@Component
@ConfigurationProperties(prefix = "game.leaderboard")
public class LeaderboardConfig {
private int maxPlayers;
private String timezone;
// getters & setters
}
You have a leaderboard service that needs to:
Limit max players (max-players
)
Support multiple regions with different timezones (timezone
)
Change limits per environment (e.g., smaller limit in dev)
application-dev.yml
game:
leaderboard:
max-players: 10
timezone: "America/New_York"
application-prod.yml
game:
leaderboard:
max-players: 1000
timezone: "UTC"
Concept | Description | Example Use Case (Gaming) |
---|---|---|
Starters | Pre-bundled dependencies | Add web, JPA, security starters for leaderboard app |
CLI | Quick prototyping using command line | Run simple Groovy API for player scores |
application.properties | External config file for app settings | Set max leaderboard size, timezone |
Profiles | Env-specific config override | Different limits in dev and prod |
@Value | Inject single property | Inject maxPlayers directly into a bean |
@ConfigurationProperties | Inject multiple related properties grouped | Group leaderboard configs into a config bean |
7️⃣ Spring Boot Profiles →
Please visit on if you wish shortcut study →https://niteshsynergy.com/spring-boot-profile
Else→
Profiles in Spring Boot allow you to group configuration and beans that should only be active in specific environments or contexts.
They help you switch between configurations easily for Dev, Test, UAT, Production, etc.
Avoids hardcoding environment-specific settings in the app.
Makes your app flexible and environment aware.
You create multiple config files named like application-{profile}.properties
or .yml
.
You can annotate beans with @Profile("profileName")
so those beans only load when that profile is active.
You activate profiles using:
JVM argument: -Dspring.profiles.active=profileName
Environment variable: SPRING_PROFILES_ACTIVE=profileName
Programmatically via code or config files
Test: Use MySQL
Production: Use MongoDB
UAT (User Acceptance Test): Use H2 (In-memory DB)
Create separate config files for each profile:
application-test.yml (MySQL)
spring:
datasource:
url: jdbc:mysql://localhost:3306/testdb
username: testuser
password: testpass
jpa:
hibernate:
ddl-auto: update
show-sql: true
application-prod.yml (MongoDB)
application-uat.yml (H2 In-memory DB)
spring:
datasource:
url: jdbc:h2:mem:uatdb;DB_CLOSE_DELAY=-1
driverClassName: org.h2.Driver
username: sa
password: password
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
@Profile
If you want to define separate beans depending on profile, annotate them:
@Configuration
public class DataSourceConfig {
@Bean
@Profile("test")
public DataSource mysqlDataSource() {
// configure and return MySQL DataSource
}
@Bean
@Profile("prod")
public MongoClient mongoClient() {
// configure and return MongoDB client
}
@Bean
@Profile("uat")
public DataSource h2DataSource() {
// configure and return H2 DataSource
}
}
You can activate profiles in multiple ways:
CLI or JVM args
java -jar app.jar --spring.profiles.active=test
# or
java -jar app.jar -Dspring.profiles.active=prod
application.properties (default profile)
spring.profiles.active=uat
Environment variable
export SPRING_PROFILES_ACTIVE=prod
You want your banking app to fetch transactions from different databases depending on environment.
On test, transactions come from MySQL
On prod, transactions come from MongoDB
On uat, use H2 for fast testing
The service layer can stay the same, Spring injects the correct datasource/bean based on profile.
Code Example: Repository Interface
public interface TransactionRepository {
List<Transaction> findByUserIdAndDateRange(String userId, LocalDate start, LocalDate end);
}
MySQL Implementation (Test Profile)
@Repository
@Profile("test")
public class MysqlTransactionRepository implements TransactionRepository {
// Use JdbcTemplate or JPA with MySQL connection
}
MongoDB Implementation (Prod Profile)
@Repository
@Profile("prod")
public class MongoTransactionRepository implements TransactionRepository {
// Use MongoTemplate or MongoRepository here
}
H2 Implementation (UAT Profile)
@Repository
@Profile("uat")
public class H2TransactionRepository implements TransactionRepository {
// Use JdbcTemplate or JPA with H2 DB
}
Complete Spring Boot Example: Banking Transactions with Profiles & Multiple DBs
Project Structure:
src/main/java/com/example/banking
├─ controller
│ └─ TransactionController.java
├─ service
│ └─ TransactionService.java
├─ repository
│ ├─ TransactionRepository.java (interface)
│ ├─ MysqlTransactionRepository.java (@Profile test)
│ ├─ MongoTransactionRepository.java (@Profile prod)
│ └─ H2TransactionRepository.java (@Profile uat)
├─ model
│ └─ Transaction.java
├─ config
│ └─ DataSourceConfig.java
└─ BankingApplication.java
1. Model - Transaction.java
package com.example.banking.model;
import java.time.LocalDate;
public class Transaction {
private String id;
private String userId;
private LocalDate date;
private double amount;
private String description;
// getters and setters
public Transaction() {}
public Transaction(String id, String userId, LocalDate date, double amount, String description) {
this.id = id;
this.userId = userId;
this.date = date;
this.amount = amount;
this.description = description;
}
// Getters and setters below
}
2. Repository Interface
package com.example.banking.repository;
import com.example.banking.model.Transaction;
import java.time.LocalDate;
import java.util.List;
public interface TransactionRepository {
List<Transaction> findByUserIdAndDateRange(String userId, LocalDate start, LocalDate end);
}
3. MySQL Implementation (test profile)
package com.example.banking.repository;
import com.example.banking.model.Transaction;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.time.LocalDate;
import java.util.List;
@Repository
@Profile("test")
public class MysqlTransactionRepository implements TransactionRepository {
private final JdbcTemplate jdbcTemplate;
public MysqlTransactionRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public List<Transaction> findByUserIdAndDateRange(String userId, LocalDate start, LocalDate end) {
String sql = "SELECT id, user_id, date, amount, description FROM transactions WHERE user_id = ? AND date BETWEEN ? AND ?";
return jdbcTemplate.query(sql, (ResultSet rs, int rowNum) -> new Transaction(
rs.getString("id"),
rs.getString("user_id"),
rs.getDate("date").toLocalDate(),
rs.getDouble("amount"),
rs.getString("description")
), userId, java.sql.Date.valueOf(start), java.sql.Date.valueOf(end));
}
}
4. MongoDB Implementation (prod profile)
package com.example.banking.repository;
import com.example.banking.model.Transaction;
import org.springframework.context.annotation.Profile;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
@Repository
@Profile("prod")
public class MongoTransactionRepository implements TransactionRepository {
private final MongoTemplate mongoTemplate;
public MongoTransactionRepository(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
@Override
public List<Transaction> findByUserIdAndDateRange(String userId, LocalDate start, LocalDate end) {
Query query = new Query();
query.addCriteria(Criteria.where("userId").is(userId)
.andOperator(Criteria.where("date").gte(start), Criteria.where("date").lte(end)));
return mongoTemplate.find(query, Transaction.class);
}
}
5. H2 Implementation (uat profile)
package com.example.banking.repository;
import com.example.banking.model.Transaction;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.time.LocalDate;
import java.util.List;
@Repository
@Profile("uat")
public class H2TransactionRepository implements TransactionRepository {
private final JdbcTemplate jdbcTemplate;
public H2TransactionRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public List<Transaction> findByUserIdAndDateRange(String userId, LocalDate start, LocalDate end) {
String sql = "SELECT id, user_id, date, amount, description FROM transactions WHERE user_id = ? AND date BETWEEN ? AND ?";
return jdbcTemplate.query(sql, (ResultSet rs, int rowNum) -> new Transaction(
rs.getString("id"),
rs.getString("user_id"),
rs.getDate("date").toLocalDate(),
rs.getDouble("amount"),
rs.getString("description")
), userId, java.sql.Date.valueOf(start), java.sql.Date.valueOf(end));
}
}
6. Service Layer
package com.example.banking.service;
import com.example.banking.model.Transaction;
import com.example.banking.repository.TransactionRepository;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
@Service
public class TransactionService {
private final TransactionRepository transactionRepository;
public TransactionService(TransactionRepository transactionRepository) {
this.transactionRepository = transactionRepository;
}
public List<Transaction> getTransactionsByUserAndDateRange(String userId, LocalDate start, LocalDate end) {
return transactionRepository.findByUserIdAndDateRange(userId, start, end);
}
}
7. Controller
package com.example.banking.controller;
import com.example.banking.model.Transaction;
import com.example.banking.service.TransactionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
@RestController
@RequestMapping("/api/transactions")
public class TransactionController {
private final TransactionService transactionService;
public TransactionController(TransactionService transactionService) {
this.transactionService = transactionService;
}
// 1. Fetch transactions by month (year-month format)
@GetMapping("/month")
public List<Transaction> getTransactionsByMonth(
@RequestParam String userId,
@RequestParam int year,
@RequestParam int month) {
LocalDate start = LocalDate.of(year, month, 1);
LocalDate end = start.withDayOfMonth(start.lengthOfMonth());
return transactionService.getTransactionsByUserAndDateRange(userId, start, end);
}
// 2. Fetch transactions by custom date range
@GetMapping("/range")
public List<Transaction> getTransactionsByDateRange(
@RequestParam String userId,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate start,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate end) {
return transactionService.getTransactionsByUserAndDateRange(userId, start, end);
}
// 3. Fetch transactions for current month
@GetMapping("/currentMonth")
public List<Transaction> getTransactionsCurrentMonth(@RequestParam String userId) {
LocalDate now = LocalDate.now();
LocalDate start = now.withDayOfMonth(1);
LocalDate end = now.withDayOfMonth(now.lengthOfMonth());
return transactionService.getTransactionsByUserAndDateRange(userId, start, end);
}
}
8. DataSourceConfig
For MySQL and H2 datasource configuration:
package com.example.banking.config;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Bean
@Profile("test")
public DataSource mysqlDataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/testdb");
ds.setUsername("testuser");
ds.setPassword("testpass");
return ds;
}
@Bean
@Profile("uat")
public DataSource h2DataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:mem:uatdb;DB_CLOSE_DELAY=-1");
ds.setUsername("sa");
ds.setPassword("");
return ds;
}
@Bean
@Profile("prod")
public MongoClient mongoClient() {
return MongoClients.create("mongodb://prodUser:prodPass@prodHost:27017/proddb");
}
}
9. Main Application Class
package com.example.banking;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BankingApplication {
public static void main(String[] args) {
SpringApplication.run(BankingApplication.class, args);
}
}
10. application.yml
spring:
profiles:
active: uat # Change this to test, prod or uat to switch environment
# Common configs can go here
Switch profile in application.yml
or via command line:
# Run with test profile (MySQL)
mvn spring-boot:run -Dspring-boot.run.profiles=test
# Run with prod profile (MongoDB)
mvn spring-boot:run -Dspring-boot.run.profiles=prod
# Run with uat profile (H2)
mvn spring-boot:run -Dspring-boot.run.profiles=uat
Next →
Spring Boot DevTools is a module that enhances the development experience by providing features like:
Automatic restart of your application on code changes.
LiveReload support to refresh your browser automatically.
Hot swapping for faster feedback.
Helpful developer-friendly error pages.
It is only intended for development, not production.
Add this dependency in your pom.xml
or build.gradle
:
Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
Gradle
developmentOnly("org.springframework.boot:spring-boot-devtools")
Hot swapping means you can update your code (e.g., Java classes, static resources) and see changes without restarting your JVM manually.
DevTools triggers automatic restart or reload of changed classes.
How it works internally:
Spring Boot runs your app in a special classloader that watches your target/classes
or build/classes
folders. When a class changes, the classloader reloads the application context.
When you modify any class or resource, DevTools automatically restarts your app to apply changes.
It’s faster than a full restart because:
Only your app classes are reloaded (not libraries).
Uses two classloaders: one for static libraries (no reload), one for app classes (reload).
It watches specific folders (src/main/java
, resources
) by default.
Configuring Restart:
You can exclude or include files from restart using spring.devtools.restart.exclude
and spring.devtools.restart.additional-paths
in application.properties
.
spring.devtools.restart.exclude=static/**,public/**
spring.devtools.restart.additional-paths=src/main/webapp
DevTools comes with built-in support for LiveReload.
When resources or templates change, it triggers your browser to reload automatically.
How to use:
Just enable the LiveReload plugin/extension in your browser (available for Chrome/Firefox), or install a LiveReload client.
By default, LiveReload runs on port 35729.
You can disable LiveReload via:
spring.devtools.livereload.enabled=false
When your app throws an exception, DevTools shows detailed error pages in the browser.
These pages contain:
Full stack trace.
Variables in scope.
Source code snippet around the error.
This is way more useful than generic 500 errors.
This is enabled by default in DevTools and disables when the app runs outside development mode.
Add devtools dependency.
Run your app.
Modify a controller or HTML template.
Notice the app automatically restarts and your browser refreshes.
Disable caching in Thymeleaf templates during development to see instant updates:
spring.thymeleaf.cache=false
Restart triggers:
DevTools restarts only on changes to .class
or .properties
files. If you add new files, you might need a manual restart.
Remote Restart:
DevTools supports remote restart where you can connect your IDE to a remote app and trigger restart.
In production — because it adds overhead and exposes detailed errors.
If you want full control over restarts (e.g., with external build tools or container-based deployments).
Feature | Description | Developer Benefit |
---|---|---|
Hot Swapping | Reload changed classes without manual restart | Faster dev cycle, instant feedback |
Auto Restart | Automatically restarts on code/resource changes | Saves time, no manual restarts |
LiveReload | Auto refresh browser on resource changes | No manual refresh needed, better front-end dev |
Error Pages | Detailed stack trace with source in browser | Easier debugging and faster issue identification |
Spring Boot uses Logback as the default logging framework.
When you create a Spring Boot project, Logback is included automatically (through spring-boot-starter
).
You do not need to configure anything to start logging; it’s ready out of the box.
Log output:
Logs are printed to the console by default with a default pattern including timestamps, log levels, thread, and message.
Logging levels define the severity and verbosity of logs. The levels in increasing order of severity are:
Level | Description | When to use |
---|---|---|
TRACE | Most detailed, logs everything including fine-grained events. | Debugging very detailed issues |
DEBUG | Less detailed than TRACE, used for debugging. | Debugging logic flow and state changes |
INFO | General application info like startup, shutdown, major events | Production useful info, app lifecycle |
WARN | Potential problems or important notices | Warnings that don’t stop app but should be noticed |
ERROR | Serious issues like exceptions | Errors that might cause failure |
OFF | Turns off logging | Disable logging |
You can configure logging level globally or per package/class using application.properties
or application.yml
.
Example: application.properties
# Global logging level
logging.level.root=INFO
# Package specific level
logging.level.com.sogdo.banking=DEBUG
# Set SQL logging to TRACE (very verbose)
logging.level.org.hibernate.SQL=TRACE
Spring Boot allows you to use your own Logback, Log4j2, or Java Util Logging configuration file instead of the default.
Place your config file under src/main/resources
:
Framework | Config file name |
---|---|
Logback | logback-spring.xml or logback.xml |
Log4j2 | log4j2-spring.xml or log4j2.xml |
Java Util Logging | logging.properties |
Create logback-spring.xml
Use org.slf4j.Logger
to log in your classes.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class BankStatementService {
private static final Logger logger = LoggerFactory.getLogger(BankStatementService.class);
public List<Transaction> getTransactions(Long userId) {
logger.info("Fetching transactions for userId: {}", userId);
try {
// logic to fetch transactions
logger.debug("Querying database for transactions");
// ...
} catch (Exception e) {
logger.error("Error fetching transactions", e);
}
return List.of(); // example
}
}
Use appropriate log levels to avoid noisy logs in production.
Use parameterized logging (logger.info("User {} logged in", userId)
) to avoid unnecessary string concatenation.
Avoid logging sensitive info like passwords or credit card numbers.
Use structured logging (JSON logs) if your infrastructure supports it for better log analysis.
Use external log management tools (like ELK stack, Splunk) to aggregate and analyze logs.
Concept | Description | Example Use Case |
---|---|---|
Default Logging | Uses Logback out of the box | Console logs with timestamp and level |
Logging Levels | TRACE, DEBUG, INFO, WARN, ERROR | DEBUG for dev, INFO for production |
External Config | Custom logback-spring.xml to tailor logging | Different logging pattern per environment |
Logger API | Use SLF4J Logger for logging in classes | logger.info("User login: {}", userId) |
@RestController
and HTTP Method Annotations@RestController
= @Controller + @ResponseBody
It means this controller handles REST APIs and returns the response body directly (usually JSON/XML) instead of resolving to a view.
HTTP method annotations define the endpoint type:
@GetMapping
– For GET requests
@PostMapping
– For POST requests
@PutMapping
– For PUT requests
@DeleteMapping
– For DELETE requests
@RestController
@RequestMapping("/api/banking")
public class TransactionController {
@GetMapping("/transactions")
public List<Transaction> getAllTransactions() {
// Return all transactions (JSON response by default)
return transactionService.getAll();
}
@PostMapping("/transaction")
public Transaction createTransaction(@RequestBody Transaction transaction) {
// Accept JSON request body, create and return transaction
return transactionService.save(transaction);
}
}
Aspect | REST Controller | MVC Controller |
---|---|---|
Purpose | Expose RESTful APIs, return data (JSON/XML) | Return Views (HTML pages, JSP, Thymeleaf) |
Annotation | @RestController | @Controller |
Response Type | JSON/XML typically | HTML View templates |
Example Use Case | Mobile apps, SPA frontend consuming JSON | Server-side rendered web pages |
Spring Boot uses Jackson library by default for JSON serialization/deserialization.
To support XML, add dependency spring-boot-starter-xml
(which brings JAXB or Jackson-dataformat-xml).
The response format depends on Accept
HTTP header sent by the client.
@GetMapping(value = "/transaction/{id}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public Transaction getTransaction(@PathVariable Long id) {
return transactionService.findById(id);
}
If client sends Accept: application/json
, response will be JSON.
If client sends Accept: application/xml
, response will be XML.
Use @RequestBody
to map HTTP request body (JSON/XML) to Java object.
Use @ResponseBody
(or @RestController
) to automatically convert Java object to JSON/XML response.
You can return Java objects or ResponseEntity<T>
for more control over HTTP status and headers.
@PostMapping("/transaction")
public ResponseEntity<Transaction> createTransaction(@RequestBody Transaction transaction) {
Transaction savedTransaction = transactionService.save(transaction);
return ResponseEntity.status(HttpStatus.CREATED).body(savedTransaction);
}
Imagine you want to provide APIs for users to fetch bank statements by:
Current month
Specific month
Date range
@RestController
@RequestMapping("/api/banking/statements")
public class BankStatementController {
@Autowired
private BankStatementService statementService;
// 1. Current month statements
@GetMapping("/current-month/{userId}")
public List<Transaction> getCurrentMonthStatement(@PathVariable Long userId) {
return statementService.getTransactionsForCurrentMonth(userId);
}
// 2. Specific month (year and month as params)
@GetMapping("/month/{userId}")
public List<Transaction> getStatementForMonth(
@PathVariable Long userId,
@RequestParam int year,
@RequestParam int month) {
return statementService.getTransactionsForMonth(userId, year, month);
}
// 3. Date range (startDate and endDate as query params)
@GetMapping("/range/{userId}")
public List<Transaction> getStatementForDateRange(
@PathVariable Long userId,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
return statementService.getTransactionsForDateRange(userId, startDate, endDate);
}
}
@PathVariable
— Used for dynamic parts of URL path, e.g., userId.
@RequestParam
— Used to pass query parameters, e.g., year, month, dates.
@DateTimeFormat
— Ensures dates in query params are parsed properly.
Annotation | Purpose | Example |
---|---|---|
@RestController | Marks class as REST controller (returns response body) | @RestController |
@GetMapping | Maps GET HTTP requests | @GetMapping("/transactions") |
@PostMapping | Maps POST HTTP requests | @PostMapping("/transaction") |
@RequestBody | Binds HTTP request body (JSON/XML) to Java object | public void save(@RequestBody Transaction txn) |
@ResponseBody | Returns Java object as response body | Usually implicit in @RestController |
@PathVariable | Extracts variable from URI path | /user/{id} , @PathVariable Long id |
@RequestParam | Extracts query parameters from URL | /transactions?year=2024&month=5 |
Spring Boot provides a default error page for any uncaught exceptions or HTTP errors.
This default error page is a whitelabel error page — a simple HTML page showing status code and error message.
It’s powered by Spring Boot’s BasicErrorController
(which implements ErrorController
interface).
Example: When a request hits an unknown URL or an exception is thrown, you get a default page like:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
There was an unexpected error (type=Not Found, status=404).
No message available
Spring Boot uses ErrorController
to map /error
path to error handling logic.
You can implement your own ErrorController
to override default behavior for all errors globally.
@Controller
public class CustomErrorController implements ErrorController {
@RequestMapping("/error")
public String handleError(HttpServletRequest request) {
// You can retrieve error status and details from request attributes
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");
// Return your own error view name
return "customErrorPage";
}
@Override
public String getErrorPath() {
return "/error";
}
}
You can customize error responses for HTML or REST APIs by creating your own error pages or JSON responses.
Create error pages in src/main/resources/templates/error/
named by HTTP status codes:
error/404.html
error/500.html
error/default.html
Spring Boot will automatically pick these pages for the respective errors.
Override error response by creating a @ControllerAdvice
or @RestControllerAdvice
with @ExceptionHandler
.
@ControllerAdvice
(Core to Spring Boot Error Handling)It’s a global exception handler for controllers.
Use it to handle exceptions across all controllers and return custom JSON or views.
Supports handling specific exceptions or all exceptions.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(TransactionNotFoundException.class)
public ResponseEntity<ErrorResponse> handleTransactionNotFound(TransactionNotFoundException ex) {
ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
System.currentTimeMillis());
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Something went wrong. Please try again.",
System.currentTimeMillis());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@RestControllerAdvice
acts globally.
@ExceptionHandler
specifies exception type to handle.
Returning custom error response with status and message.
public class ErrorResponse {
private int status;
private String message;
private long timeStamp;
// constructors, getters, setters
}
Spring Boot Actuator provides production-ready features to monitor and manage your Spring Boot application.
It exposes endpoints over HTTP or JMX to provide info about application health, metrics, environment, etc.
Helps developers & ops teams monitor, troubleshoot, and gain insights without custom coding.
Easy monitoring of app health
Track metrics (requests, CPU, memory)
View configuration properties & environment
Trace HTTP requests
Audit app events and much more
Add the dependency in pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
By default, some endpoints are enabled (like /actuator/health
, /actuator/info
), others are disabled or restricted.
Endpoint | Description | Default Exposure |
---|---|---|
/actuator/health | Shows app health status | Public |
/actuator/info | Custom application info | Public |
/actuator/metrics | Metrics like memory, CPU, etc. | Restricted |
/actuator/httptrace | Recent HTTP requests | Restricted |
/actuator/env | Environment properties | Restricted |
The /actuator/health
endpoint gives you the health status of the app: UP
, DOWN
, OUT_OF_SERVICE
, or UNKNOWN
.
You can add custom health indicators to report health of dependencies like databases or external services.
@Component
public class BankDbHealthIndicator implements HealthIndicator {
@Override
public Health health() {
// Check if bank database is reachable
boolean dbUp = checkDatabaseConnection(); // your logic here
if (dbUp) {
return Health.up().withDetail("Bank DB", "Available").build();
} else {
return Health.down().withDetail("Bank DB", "Not reachable").build();
}
}
private boolean checkDatabaseConnection() {
// Implementation: ping DB or query a simple select 1
return true; // assuming OK here
}
}
The /actuator/metrics
endpoint exposes key performance metrics.
You can query metrics by name, e.g. /actuator/metrics/jvm.memory.used
.
You can create your own actuator endpoints to expose custom info or operations.
@Component
@Endpoint(id = "transactionCount")
public class TransactionCountEndpoint {
private final TransactionService transactionService;
public TransactionCountEndpoint(TransactionService transactionService) {
this.transactionService = transactionService;
}
@ReadOperation
public int transactionsToday() {
return transactionService.countTransactionsForToday();
}
}
@Component
@Endpoint(id = "transactionCount")
public class TransactionCountEndpoint {
private final TransactionService transactionService;
public TransactionCountEndpoint(TransactionService transactionService) {
this.transactionService = transactionService;
}
@ReadOperation
public int transactionsToday() {
return transactionService.countTransactionsForToday();
}
}
Exposed at /actuator/transactionCount
You can add @WriteOperation
or @DeleteOperation
for other REST verbs.
By default, sensitive actuator endpoints like /metrics
and /env
are not exposed publicly.
application.properties
:
management.endpoints.web.exposure.include=health,info,metrics,env,transactionCount
Minimal security example:
@Configuration
public class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR_ADMIN")
.and()
.httpBasic();
}
}
This requires users to have role ACTUATOR_ADMIN
to access actuator endpoints.
Configure user in application.properties
or via other security mechanisms.
Next→
Spring Boot provides powerful testing support to validate layers (controller, service, repository) with minimal configuration using special annotations called Test Slices.
@SpringBootTest
Loads the full application context (like production).
Best for integration tests across all layers.
@SpringBootTest
public class BankingIntegrationTest {
@Autowired
private TransactionService transactionService;
@Test
void testMonthlyStatementFetch() {
List<Transaction> list = transactionService.getTransactionsByMonth("2024-12");
assertEquals(10, list.size());
}
}
🔧 Use when: Testing multiple beans (controller + service + repo + config)
@WebMvcTest
Loads only Controller layer + minimal web config.
No @Service
, @Repository
loaded unless manually mocked.
@WebMvcTest(TransactionController.class)
public class TransactionControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private TransactionService transactionService;
@Test
void testGetCurrentMonthTransactions() throws Exception {
when(transactionService.getCurrentMonthTransactions()).thenReturn(mockedList());
mockMvc.perform(get("/transactions/current-month"))
.andExpect(status().isOk());
}
}
🔧 Use when: You want to test only Controller + Request/Response mapping.
@DataJpaTest
Loads only JPA repository and DB layer (uses embedded H2 by default).
Useful for DB query testing without loading full app.
@DataJpaTest
public class TransactionRepositoryTest {
@Autowired
private TransactionRepository transactionRepo;
@Test
void testFindByMonth() {
transactionRepo.save(new Transaction(...));
List<Transaction> result = transactionRepo.findByMonth("2025-01");
assertFalse(result.isEmpty());
}
}
🔧 Use when: Testing DB logic in isolation.
Use @ContextConfiguration
to load custom context in special cases.
Default behavior in Spring Boot is to auto-detect main config class.
By default, Spring Boot builds fat JAR with embedded server.
In pom.xml
:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
To build:
mvn clean package
To run:
java -jar target/banking-app-1.0.jar
If deploying to external Tomcat server (rare today):
Set packaging to war
Extend SpringBootServletInitializer
Provide configure()
method
Spring Boot supports overriding config without rebuilding the app:
java -jar banking-app.jar --spring.config.location=/opt/config/
or via env variables:
SPRING_DATASOURCE_URL=jdbc:mysql://... java -jar ...
Spring Boot supports:
Server | Dependency |
---|---|
Tomcat (default) | spring-boot-starter-web |
Jetty | spring-boot-starter-jetty |
Undertow | spring-boot-starter-undertow |
To switch:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope> <!-- disables embedded tomcat -->
</dependency>
CommandLineRunner
& ApplicationRunner
Used to execute logic at startup.
Use-case: Initializing DB or caching configs
@Component
public class StartupRunner implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("Initializing core banking APIs...");
}
}
@Component
public class StartupRunner implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("Initializing core banking APIs...");
}
}
ApplicationRunner
is similar, but provides ApplicationArguments
.
Spring Boot (2.3+) handles graceful shutdown using:
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
You can hook into shutdown:
@PreDestroy
public void onDestroy() {
// cleanup banking transactions or close DBs
}
SPring Banners last topic you can do it own.
end → Please support our work on https://razorpay.me/@niteshsynergy
Your email address will not be published. Required fields are marked *