Spring Boot
Advantages of Spring Boot
Spring Boot's advantages go beyond saving setup time. It changes how teams build, test, deploy, and operate Java applications — from zero-configuration startup to production-ready monitoring, all with sensible defaults that stay out of your way when you don't need them.
1. Auto-Configuration — Zero Boilerplate Setup
Spring Boot's auto-configuration analyzes your classpath and automatically configures the Spring application context. Add a dependency, get a working configuration. This eliminates the single biggest pain point in traditional Spring: hours of XML and Java config writing before business logic could be touched.
Java
// Traditional Spring — required for a web app with JPA:
// web.xml (30+ lines)
// applicationContext.xml (50+ lines)
// dispatcher-servlet.xml (30+ lines)
// persistence.xml (20+ lines)
// Manual bean definitions for:
// - DataSource, EntityManagerFactory, TransactionManager
// - ViewResolver, MessageConverter, ExceptionHandler
// - PropertySourcesPlaceholderConfigurer
// Spring Boot — the same app:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// application.properties:
// spring.datasource.url=jdbc:mysql://localhost/mydb
// spring.datasource.username=root
// spring.datasource.password=secret
// That's it. Spring Boot configures everything else automatically.2. Embedded Server — Deploy Anywhere
Spring Boot embeds Tomcat, Jetty, or Undertow directly in the application JAR. Your application becomes a self-contained executable that runs with java -jar — no external server installation, no WAR deployment, no server configuration files.
XML
// Build a self-contained JAR:
// mvn clean package → myapp.jar (~30MB — includes Tomcat + all dependencies)
// Run it — anywhere Java is installed:
java -jar myapp.jar
// Switch the embedded server — just change the dependency:
// Default: spring-boot-starter-web (includes Tomcat)
// Switch to Jetty:
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
// Server config in application.properties — no server.xml needed: server.port=8080 server.tomcat.max-threads=200 server.tomcat.connection-timeout=50003. Starter Dependencies — Curated Dependency Management
Spring Boot starters are curated sets of dependencies that work together at tested, compatible versions. Instead of manually researching which versions of 15 libraries are compatible, you add one starter and everything works.
XML
<!-- Traditional Spring — manually find and version-match everything: -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.11</version> <!-- must match other Spring versions -->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version> <!-- must be compatible with Spring 6 -->
</dependency>
<!-- + 10 more dependencies, all manually version-managed -->
<!-- Spring Boot — one starter, everything included and version-matched: -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- No version needed — Spring Boot's parent POM manages all versions -->
</dependency>
<!-- Common starters:
spring-boot-starter-web → MVC + Tomcat + Jackson
spring-boot-starter-data-jpa → Hibernate + Spring Data JPA + HikariCP
spring-boot-starter-security → Spring Security
spring-boot-starter-test → JUnit 5 + Mockito + AssertJ + MockMVC
spring-boot-starter-data-redis → Spring Data Redis + Lettuce
spring-boot-starter-amqp → Spring AMQP + RabbitMQ
spring-boot-starter-mail → JavaMailSender
spring-boot-starter-actuator → Production monitoring endpoints -->4. Production-Ready with Actuator
Spring Boot Actuator adds production monitoring and management endpoints to any Spring Boot application with a single dependency. Health checks, metrics, environment info, thread dumps — all exposed via HTTP or JMX.
Java
<!-- Add Actuator: -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
// Immediately available endpoints:
// GET /actuator/health — application health status
// GET /actuator/info — application info
// GET /actuator/metrics — JVM and application metrics
// GET /actuator/env — environment properties
// GET /actuator/beans — all Spring beans
// GET /actuator/mappings — all @RequestMapping routes
// POST /actuator/shutdown — graceful shutdown
// Enable all endpoints in application.properties:
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
// Sample /actuator/health response:
// {
// "status": "UP",
// "components": {
// "db": { "status": "UP", "details": { "database": "MySQL" } },
// "diskSpace": { "status": "UP", "details": { "free": "50GB" } }
// }
// }
// Custom health indicator:
@Component
public class PaymentServiceHealthIndicator implements HealthIndicator {
@Override
public Health health() {
boolean up = paymentGateway.isReachable();
return up ? Health.up().build()
: Health.down().withDetail("reason", "Gateway unreachable").build();
}
}5. Developer Experience — DevTools and Fast Feedback
Spring Boot DevTools dramatically speeds up the development loop. Automatic application restart on code changes, LiveReload for browser refresh, and remote debugging support make the edit-test cycle fast.
XML
<!-- Add DevTools (automatically disabled in production): -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
// What DevTools provides:
// 1. Automatic restart — application restarts when classpath changes (file save)
// Much faster than a full restart — uses two classloaders:
// base classloader (libraries, unchanged) + restart classloader (your code)
// Only the restart classloader is reloaded — typically 1-2 seconds
// 2. LiveReload — embedded server that triggers browser refresh on restart
// Install the LiveReload browser extension to enable
// 3. Property defaults for development:
// spring.thymeleaf.cache=false (no template caching)
// spring.web.resources.cache.period=0 (no static resource caching)
// logging.level.web=DEBUG
// 4. Remote restart — restart a remote application via HTTP tunnel
// Useful for remote development environments
// application.properties DevTools settings: spring.devtools.restart.enabled=true spring.devtools.restart.additional-paths=src/main/java spring.devtools.livereload.enabled=true6. Opinionated Defaults That Stay Out of Your Way
Spring Boot's philosophy is "opinionated but not dictatorial." It makes sensible default choices so you don't have to — but every default can be overridden when your requirements differ.
XML
// Spring Boot chooses these defaults — all overridable:
// Web server: Tomcat (override: use Jetty or Undertow)
// JSON: Jackson (override: use Gson)
// Connection pool: HikariCP (override: use c3p0 or DBCP2)
// Logging: Logback via SLF4J (override: use Log4j2)
// Test: JUnit 5 (override: use JUnit 4 with exclusion)
// Override a default — HikariCP → DBCP2:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
// Spring Boot detects DBCP2 and auto-configures it instead of HikariCP
// Override Jackson configuration:
@Configuration
public class JacksonConfig {
@Bean
@Primary
public ObjectMapper objectMapper() {
return JsonMapper.builder()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.addModule(new JavaTimeModule())
.build();
}
}7. Profile-Based Configuration
Spring Boot's profile system makes environment-specific configuration clean and explicit. Different settings for development, staging, and production — with no code changes between environments.
application.properties
// application.properties — common settings:
app.name=MyApplication
spring.jpa.show-sql=false
// application-dev.properties — development overrides:
server.port=8080
spring.datasource.url=jdbc:h2:mem:testdb
spring.jpa.show-sql=true
logging.level.com.example=DEBUG
// application-prod.properties — production settings:
server.port=80
spring.datasource.url=jdbc:mysql://prod-db:3306/mydb
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
logging.level.root=WARN
// Activate a profile:
java -jar app.jar --spring.profiles.active=prod
// or set environment variable: SPRING_PROFILES_ACTIVE=prod
// Profile-specific beans — only created in certain environments:
@Configuration
@Profile("dev")
public class DevDataInitializer {
@Bean
CommandLineRunner seedTestData(UserRepository repo) {
return args -> {
repo.save(new User("admin@test.com", "Admin"));
repo.save(new User("user@test.com", "Test User"));
};
}
}
// @Profile("!prod") — active in every profile EXCEPT prod:
@Component
@Profile("!prod")
public class MockPaymentGateway implements PaymentGateway { }8. First-Class Testing Support
Spring Boot's testing support makes writing integration tests straightforward. @SpringBootTest loads the full application context, MockMVC tests controllers without a real server, and @DataJpaTest loads only the persistence layer for repository tests.
Java
// Full integration test — loads entire Spring Boot application:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void createUser_returnsCreatedUser() {
CreateUserRequest request = new CreateUserRequest("alice@example.com", "Alice");
ResponseEntity<User> response = restTemplate.postForEntity("/users", request, User.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getBody().getEmail()).isEqualTo("alice@example.com");
}
}
// Controller slice test — loads only MVC layer, no DB:
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void getUser_returnsUser() throws Exception {
when(userService.findById(1L)).thenReturn(new User(1L, "Alice"));
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Alice"));
}
}
// Repository slice test — loads only JPA layer:
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void findByEmail_returnsUser() {
userRepository.save(new User("alice@example.com", "Alice"));
Optional<User> found = userRepository.findByEmail("alice@example.com");
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("Alice");
}
}