Spring BootSpring Boot Features
Spring Boot

Spring Boot Features

Spring Boot ships with a rich set of features that go well beyond auto-configuration and embedded servers. Externalized configuration, profiles, Actuator, DevTools, graceful shutdown, and built-in testing support together form a complete platform for production Java applications.

What Makes Spring Boot a Complete Platform

Spring Boot is more than a shortcut for configuring Spring Framework. It's a full platform for building, running, and operating Java applications in production. Its features cover the entire application lifecycle — from writing code (DevTools), to configuring environments (profiles, externalized config), to running in production (Actuator, graceful shutdown), to testing (sliced test contexts). Understanding these features individually and together is what separates developers who use Spring Boot as a configuration shortcut from developers who leverage it as a complete platform.

1. Auto-Configuration

Auto-configuration is Spring Boot's signature feature. It inspects your classpath, existing beans, and property settings, then automatically configures Spring beans to match what your application likely needs. Every auto-configuration is conditional — it backs off the moment you define your own bean.
Java
// Auto-configuration is driven by @Conditional annotations:
// Add spring-boot-starter-data-jpa → Spring Boot automatically configures:
//   - HikariCP connection pool (from application.properties)
//   - Hibernate EntityManagerFactory
//   - Spring Data JPA repositories
//   - JPA transaction manager

// All you write is:
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=secret

// And your repository interface:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}
// Spring Boot generates the implementation — zero config required

// Override any auto-configured bean by defining your own:
@Bean
@Primary
public DataSource customDataSource() {
    // Spring Boot detects this bean and skips its own DataSource auto-config
    return DataSourceBuilder.create()
        .url("jdbc:mysql://localhost:3306/mydb")
        .username("root")
        .password("secret")
        .type(HikariDataSource.class)
        .build();
}

// Debug auto-configuration decisions:
// application.properties: debug=true
// Prints POSITIVE MATCHES (applied) and NEGATIVE MATCHES (skipped) on startup

// Exclude specific auto-configurations:
@SpringBootApplication(exclude = {
    SecurityAutoConfiguration.class,
    DataSourceAutoConfiguration.class
})
public class Application { }

2. Externalized Configuration

Spring Boot provides a unified, hierarchical configuration system. The same application JAR can be configured differently for each environment without any code changes — properties come from files, environment variables, command-line arguments, and more, with a strict precedence order.
Java
// Configuration source precedence (highest to lowest):
// 1. Command-line arguments:           --server.port=9090
// 2. OS environment variables:         SERVER_PORT=9090
// 3. application-{profile}.properties  (classpath)
// 4. application.properties            (classpath)
// 5. @PropertySource annotations
// 6. Default properties

// application.properties — flat key-value:
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
app.jwt.expiration=86400

// application.yml — hierarchical (same thing, different syntax):
server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
app:
  jwt:
    expiration: 86400

// Reference environment variables to avoid hardcoding secrets:
spring.datasource.password=${DB_PASSWORD}
app.stripe.key=${STRIPE_SECRET_KEY}

// Bind a group of properties to a typed class:
@ConfigurationProperties(prefix = "app.mail")
@Component
public class MailProperties {
    private String host = "smtp.example.com";
    private int port = 587;
    private String username;
    private String password;
    private boolean ssl = true;
    // getters and setters
}

// Validate configuration at startup:
@ConfigurationProperties(prefix = "app.jwt")
@Validated
public class JwtProperties {
    @NotBlank
    private String secret;

    @Min(300)
    private long expirationSeconds = 3600;
    // getters and setters
}

3. Profiles

Profiles allow environment-specific configuration — separate settings for development, testing, staging, and production — all in the same codebase. Spring Boot loads profile-specific property files automatically and can conditionally create beans per profile.
application.properties
// File naming convention:
// application.properties          — loaded for ALL profiles
// application-dev.properties      — loaded when profile 'dev' is active
// application-prod.properties     — loaded when profile 'prod' is active
// application-test.properties     — loaded when profile 'test' is active

// application-dev.properties:
spring.datasource.url=jdbc:h2:mem:devdb
spring.h2.console.enabled=true
spring.jpa.show-sql=true
logging.level.com.example=DEBUG

// application-prod.properties:
spring.datasource.url=${DATABASE_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.jpa.show-sql=false
logging.level.root=WARN

// Profile-specific beans:
@Configuration
public class StorageConfig {

    @Bean
    @Profile("dev")
    public StorageService localStorageService() {
        return new LocalFileStorageService("/tmp/uploads");
    }

    @Bean
    @Profile("prod")
    public StorageService s3StorageService(AwsProperties props) {
        return new S3StorageService(props.getBucket(), props.getRegion());
    }
}

// Activate a profile:
// In application.properties:      spring.profiles.active=dev
// Command line:                   java -jar app.jar --spring.profiles.active=prod
// Environment variable:           SPRING_PROFILES_ACTIVE=prod
// In tests:                       @ActiveProfiles("test")

// Profile groups (Spring Boot 2.4+):
// spring.profiles.group.production=prod,metrics,monitoring
// Activating 'production' activates all three profiles at once

// @Profile with NOT:
@Component
@Profile("!prod")   // active in every profile EXCEPT prod
public class MockPaymentService implements PaymentService { }

4. Embedded Web Server

Spring Boot embeds the web server directly inside the application JAR. No external Tomcat installation, no WAR deployment, no server configuration files. Your application is a self-contained executable that runs on any machine with Java installed.
XML
// Default embedded server: Tomcat (via spring-boot-starter-web)
// Alternatives: Jetty, Undertow — swap by excluding Tomcat and adding another

// 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>

// Configure the server in application.properties: server.port=8080 server.servlet.context-path=/api server.tomcat.max-threads=200 server.tomcat.connection-timeout=5000 server.compression.enabled=true server.compression.min-response-size=1024

// HTTPS configuration: server.ssl.key-store=classpath:keystore.p12 server.ssl.key-store-password=changeit server.ssl.key-store-type=PKCS12 server.ssl.key-alias=myapp

// Programmatic server customization:
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
    return factory -> {
        factory.setPort(8080);
        factory.setConnectionTimeout(Duration.ofSeconds(10));
        factory.addConnectorCustomizers(connector ->
            connector.setMaxPostSize(10 * 1024 * 1024)  // 10MB max request
        );
    };
}

5. Spring Boot Actuator

Actuator adds production-ready monitoring and management to any Spring Boot application. Health checks, metrics, environment info, thread dumps, and live log-level changes are available immediately after adding the dependency — no additional code required.
XML
<!-- Add to pom.xml: -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

// Expose all endpoints (development): management.endpoints.web.exposure.include=* management.endpoint.health.show-details=always

// Key endpoints available immediately:
// GET  /actuator/health    — application + component health status
// GET  /actuator/info      — app info from application.properties
// GET  /actuator/metrics   — JVM and application metrics list
// GET  /actuator/metrics/jvm.memory.used  — specific metric with tags
// GET  /actuator/env       — all properties and which file they came from
// GET  /actuator/beans     — every Spring bean and its dependencies
// GET  /actuator/mappings  — all @RequestMapping routes in the app
// POST /actuator/loggers/com.example  — change log level at runtime (no restart!)
// GET  /actuator/threaddump — current JVM thread dump
// GET  /actuator/heapdump  — download heap dump (attach to profiler)

// Custom health indicator:
@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {

    private final PaymentGatewayClient client;

    @Override
    public Health health() {
        try {
            long latency = client.ping();
            return Health.up()
                .withDetail("latency_ms", latency)
                .withDetail("endpoint", client.getEndpoint())
                .build();
        } catch (Exception e) {
            return Health.down()
                .withDetail("reason", "Gateway unreachable")
                .withException(e)
                .build();
        }
    }
}

// Custom metric:
@Service
public class OrderService {

    private final Counter orderCounter;
    private final Timer orderTimer;

    public OrderService(MeterRegistry meterRegistry) {
        this.orderCounter = Counter.builder("orders.created")
            .tag("source", "api")
            .description("Total orders created")
            .register(meterRegistry);

        this.orderTimer = Timer.builder("orders.processing.time")
            .description("Time to process an order")
            .register(meterRegistry);
    }

    public Order createOrder(OrderRequest request) {
        return orderTimer.record(() -> {
            Order order = processOrder(request);
            orderCounter.increment();
            return order;
        });
    }
}

6. Spring Boot DevTools

DevTools dramatically speeds up the development feedback loop. Code changes trigger an automatic application restart in 1-2 seconds (versus a full 10+ second restart), browser pages reload automatically, and development-friendly defaults like disabled template caching are activated automatically.
XML
<!-- Add to pom.xml (optional=true ensures it's excluded from production JAR): -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

// What DevTools enables automatically:

// 1. Fast restart — only the app classloader restarts (not the JVM or libraries)
//    Typical restart: 1-2 seconds vs 10-15 seconds without DevTools
//    Triggered: every time you save a file in your IDE

// 2. LiveReload server — embedded on port 35729
//    Install the LiveReload browser extension → page refreshes on app restart

// 3. Development property defaults activated automatically:
//    spring.thymeleaf.cache=false          (no need to restart for template changes)
//    spring.freemarker.cache=false
//    spring.web.resources.cache.period=0  (static files always fresh)
//    spring.mvc.log-resolved-exception=true
//    logging.level.web=DEBUG

// 4. H2 console auto-enabled at /h2-console (when H2 is on the classpath)

// Configure what triggers a restart: spring.devtools.restart.additional-paths=src/main/java spring.devtools.restart.exclude=static/**,public/**,templates/**

// Disable restart (LiveReload only): spring.devtools.restart.enabled=false

// DevTools is automatically disabled in production:
// — When running a packaged JAR: java -jar app.jar
// — When a specific classloader is detected (production server classloaders)
// — No need to manually remove it before deploying

7. Graceful Shutdown

When a Spring Boot application receives a shutdown signal (SIGTERM in Kubernetes, Ctrl+C in terminal), graceful shutdown stops the server from accepting new requests and waits for in-flight requests to complete before closing — preventing incomplete transactions and corrupted responses.
application.properties
// Enable graceful shutdown:
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s

// Shutdown sequence:
// 1. SIGTERM received (Kubernetes scales down, systemd stops service, etc.)
// 2. DispatcherServlet stops accepting new incoming requests
// 3. Spring waits up to 30s for active requests to complete
// 4. @PreDestroy methods called on all beans (reverse creation order)
// 5. ApplicationContext closes
// 6. JVM exits with code 0

// Cleanup code in beans:
@Component
public class DatabaseConnectionPool {

    @PreDestroy
    public void shutdown() {
        log.info("Closing connection pool...");
        pool.close();
        log.info("Connection pool closed");
    }
}

// Listen to shutdown events for more control:
@Component
public class MessageQueueShutdownHandler {

    @EventListener(ContextClosedEvent.class)
    public void onShutdown() {
        log.info("Draining message queue...");
        queue.drain(Duration.ofSeconds(10));
    }
}

// DisposableBean interface alternative:
@Component
public class CacheManager implements DisposableBean {
    @Override
    public void destroy() {
        log.info("Persisting cache to disk before shutdown...");
        cache.flush();
    }
}

// In Kubernetes — pair with preStop hook for zero-downtime:
// lifecycle:
//   preStop:
//     exec:
//       command: ["sh", "-c", "sleep 5"]
// Give the load balancer 5s to stop routing traffic before SIGTERM

8. Spring Boot Testing

Spring Boot's testing support provides sliced test contexts — load only the layers you need. Test the full stack with @SpringBootTest, just the MVC layer with @WebMvcTest, or just the persistence layer with @DataJpaTest. Each slice loads only the relevant beans, keeping tests fast.
Java
// Full integration test — loads entire application context:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
class UserIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void createUser_returnsCreatedStatus() {
        CreateUserRequest req = new CreateUserRequest("alice@test.com", "Alice", "password123");
        ResponseEntity<UserResponse> res = restTemplate
            .postForEntity("/api/users", req, UserResponse.class);

        assertThat(res.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(res.getBody().email()).isEqualTo("alice@test.com");
    }
}

// Controller slice test — only loads MVC layer, no DB:
@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void getUser_returnsUser() throws Exception {
        when(userService.findById(1L))
            .thenReturn(Optional.of(new UserResponse(1L, "alice@test.com", "Alice")));

        mockMvc.perform(get("/api/users/1").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.email").value("alice@test.com"))
            .andExpect(jsonPath("$.name").value("Alice"));
    }

    @Test
    void createUser_withInvalidEmail_returnsBadRequest() throws Exception {
        CreateUserRequest invalid = new CreateUserRequest("not-an-email", "Alice", "pass");

        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(invalid)))
            .andExpect(status().isBadRequest());
    }
}

// Repository slice test — loads only JPA layer with in-memory H2:
@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void findByEmail_returnsUser() {
        userRepository.save(new User("alice@test.com", "Alice", "hashed"));

        Optional<User> found = userRepository.findByEmail("alice@test.com");

        assertThat(found).isPresent();
        assertThat(found.get().getName()).isEqualTo("Alice");
    }

    @Test
    void existsByEmail_returnsTrueForExistingEmail() {
        userRepository.save(new User("bob@test.com", "Bob", "hashed"));
        assertThat(userRepository.existsByEmail("bob@test.com")).isTrue();
        assertThat(userRepository.existsByEmail("nobody@test.com")).isFalse();
    }
}

9. Spring Boot CLI

Spring Boot CLI (Command Line Interface) lets you write and run Groovy or Java scripts that use Spring Boot features — without a full Maven or Gradle project. It's useful for rapid prototyping, testing ideas, and running simple utility scripts.
Shell
# Install Spring Boot CLI via SDKMAN:
sdk install springboot

# Verify:
spring --version

# Write a Groovy script — app.groovy:
# @RestController
# class HelloController {
#     @RequestMapping("/")
#     String hello() { "Hello, Spring Boot CLI!" }
# }

# Run it directly — no pom.xml, no main method, no imports:
spring run app.groovy

# Visit http://localhost:8080 → "Hello, Spring Boot CLI!"

# Spring Boot CLI adds all necessary imports automatically
# It detects annotations and adds the right dependencies

# Run with custom port:
spring run app.groovy -- --server.port=9090

# Test a Groovy script:
spring test tests.groovy

# Package a Groovy app as a JAR:
spring jar myapp.jar app.groovy
java -jar myapp.jar

# Initialize a new Spring Boot project from the CLI:
spring init --dependencies=web,data-jpa,security myproject
# Same as using start.spring.io — generates a complete Maven project

10. Spring Initializr Integration

Spring Boot applications are typically bootstrapped through Spring Initializr — a web tool at start.spring.io that generates a complete, ready-to-run project with your chosen dependencies. It's also integrated directly into IntelliJ IDEA and VS Code.
Shell
# Generate a project via Spring Initializr API (curl):
curl https://start.spring.io/starter.zip \
  -d type=maven-project \
  -d language=java \
  -d bootVersion=3.1.4 \
  -d baseDir=myapp \
  -d groupId=com.example \
  -d artifactId=myapp \
  -d name=myapp \
  -d packageName=com.example.myapp \
  -d javaVersion=21 \
  -d dependencies=web,data-jpa,security,actuator,validation,devtools \
  -o myapp.zip

unzip myapp.zip && cd myapp

# Generated project structure:
# myapp/
# ├── src/
# │   ├── main/
# │   │   ├── java/com/example/myapp/
# │   │   │   └── MyappApplication.java
# │   │   └── resources/
# │   │       ├── application.properties
# │   │       ├── static/
# │   │       └── templates/
# │   └── test/
# │       └── java/com/example/myapp/
# │           └── MyappApplicationTests.java
# ├── pom.xml
# └── mvnw  (Maven wrapper — run Maven without installing it)

# Run with Maven wrapper (no Maven installation needed):
./mvnw spring-boot:run

# IntelliJ IDEA: File → New Project → Spring Initializr
# VS Code: Command Palette → Spring Initializr: Create a Maven Project

11. Logging

Spring Boot auto-configures Logback as the default logging framework via SLF4J. Log levels, output format, file output, and rolling policies are all configurable through application.properties — no logback.xml needed for common setups.
Java
// Default log format:
// 2023-10-15 14:32:01.234  INFO 12345 --- [main] com.example.Application : Started Application

// Configure log levels in application.properties:
logging.level.root=INFO
logging.level.com.example=DEBUG
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate.SQL=DEBUG          // log all SQL queries
logging.level.org.hibernate.type=TRACE         // log SQL parameter values

// Write logs to a file:
logging.file.name=logs/application.log
logging.file.max-size=10MB
logging.file.max-history=30                   // keep 30 days of logs
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n

// Using SLF4J in your code:
@Service
public class UserService {

    private static final Logger log = LoggerFactory.getLogger(UserService.class);
    // Or with Lombok: @Slf4j on the class

    public User createUser(CreateUserRequest request) {
        log.debug("Creating user with email: {}", request.email());

        User user = userRepository.save(new User(request));
        log.info("User created: id={}, email={}", user.getId(), user.getEmail());

        return user;
    }
}

// Switch to Log4j2 (better async performance):
// 1. Exclude spring-boot-starter-logging from spring-boot-starter
// 2. Add spring-boot-starter-log4j2
// 3. Create log4j2.xml in src/main/resources