1. java
  2. /spring
  3. /spring-boot

Master Spring Boot Development

Spring Boot

Spring Boot is a powerful framework that simplifies Spring application development by providing auto-configuration, embedded servers, and production-ready features out of the box. It follows the "convention over configuration" principle, allowing developers to create stand-alone, production-grade Spring applications with minimal setup.

What is Spring Boot?

Spring Boot builds on top of the Spring Framework to provide:

Key Features:

  • Auto-Configuration: Automatically configures Spring beans based on classpath dependencies
  • Starter Dependencies: Pre-configured dependency bundles for common use cases
  • Embedded Servers: Built-in Tomcat, Jetty, or Undertow servers
  • Production-Ready: Built-in metrics, health checks, and externalized configuration
  • No XML Configuration: Convention-based configuration with annotations

Benefits:

  • Rapid Development: Get applications running quickly with minimal configuration
  • Microservices Ready: Perfect for building microservices architectures
  • Production Features: Monitoring, security, and deployment capabilities built-in
  • Developer Experience: Excellent tooling support and hot reloading
  • Community Ecosystem: Large ecosystem of starters and extensions
// Traditional Spring application setup (verbose)
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example")
public class WebConfig implements WebMvcConfigurer {
    
    @Bean
    public DataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:h2:mem:testdb");
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        return dataSource;
    }
    
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    
    // Many more configuration beans...
}

// Spring Boot application (simplified)
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

Getting Started with Spring Boot

Creating a Spring Boot application is straightforward:

Project Structure

my-spring-boot-app/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/
│   │   │       ├── MyApplication.java
│   │   │       ├── controller/
│   │   │       │   └── UserController.java
│   │   │       ├── service/
│   │   │       │   └── UserService.java
│   │   │       └── model/
│   │   │           └── User.java
│   │   └── resources/
│   │       ├── application.properties
│   │       ├── static/
│   │       └── templates/
│   └── test/
│       └── java/
├── target/
└── pom.xml

Main Application Class

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication // Combines @Configuration, @EnableAutoConfiguration, @ComponentScan
public class MyApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

Basic REST Controller

package com.example.controller;

import org.springframework.web.bind.annotation.*;
import java.util.*;

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserService userService;
    
    // Constructor injection (recommended)
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.findAll();
    }
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id)
                .orElseThrow(() -> new UserNotFoundException("User not found: " + id));
    }
    
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
    
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        return userService.update(id, user);
    }
    
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteById(id);
    }
}

Service Layer

package com.example.service;

import org.springframework.stereotype.Service;
import java.util.*;

@Service
public class UserService {
    
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public List<User> findAll() {
        return userRepository.findAll();
    }
    
    public Optional<User> findById(Long id) {
        return userRepository.findById(id);
    }
    
    public User save(User user) {
        validateUser(user);
        return userRepository.save(user);
    }
    
    public User update(Long id, User user) {
        User existing = findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found: " + id));
        
        existing.setName(user.getName());
        existing.setEmail(user.getEmail());
        return userRepository.save(existing);
    }
    
    public void deleteById(Long id) {
        if (!userRepository.existsById(id)) {
            throw new UserNotFoundException("User not found: " + id);
        }
        userRepository.deleteById(id);
    }
    
    private void validateUser(User user) {
        if (user.getName() == null || user.getName().trim().isEmpty()) {
            throw new IllegalArgumentException("User name is required");
        }
        if (user.getEmail() == null || !user.getEmail().contains("@")) {
            throw new IllegalArgumentException("Valid email is required");
        }
    }
}

Spring Boot Starters

Starters are dependency descriptors that bring in all necessary dependencies for specific functionality:

Common Starters

<!-- pom.xml -->
<dependencies>
    <!-- Web applications -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Database access with JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
    <!-- Testing -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!-- Validation -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    
    <!-- Actuator for monitoring -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>

Database Integration Example

// Entity
package com.example.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @NotBlank(message = "Name is required")
    @Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")
    private String name;
    
    @Email(message = "Invalid email format")
    @NotBlank(message = "Email is required")
    @Column(unique = true)
    private String email;
    
    @Min(value = 0, message = "Age must be non-negative")
    @Max(value = 150, message = "Age must be realistic")
    private Integer age;
    
    // Constructors
    public User() {}
    
    public User(String name, String email, Integer age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }
    
    // Getters and setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    
    @Override
    public String toString() {
        return "User{id=" + id + ", name='" + name + "', email='" + email + "', age=" + age + "}";
    }
}

// Repository
package com.example.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    // Spring Data JPA provides basic CRUD operations automatically
    
    // Custom query methods
    Optional<User> findByEmail(String email);
    List<User> findByNameContainingIgnoreCase(String name);
    List<User> findByAgeBetween(Integer minAge, Integer maxAge);
    
    // Custom JPQL query
    @Query("SELECT u FROM User u WHERE u.age > ?1 ORDER BY u.name")
    List<User> findUsersOlderThan(Integer age);
    
    // Native SQL query
    @Query(value = "SELECT * FROM users WHERE email LIKE %?1%", nativeQuery = true)
    List<User> findByEmailContaining(String emailPart);
}

Configuration and Properties

Spring Boot provides flexible configuration options:

Application Properties

# application.properties

# Server configuration
server.port=8080
server.servlet.context-path=/api

# Database configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

# JPA configuration
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

# H2 Console (for development)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# Logging configuration
logging.level.com.example=DEBUG
logging.level.org.springframework.web=INFO
logging.pattern.console=%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n

# Actuator endpoints
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always

Configuration Classes

package com.example.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "app")
public class AppConfig {
    
    private String name = "My Application";
    private String version = "1.0.0";
    private Security security = new Security();
    
    // Getters and setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public String getVersion() { return version; }
    public void setVersion(String version) { this.version = version; }
    
    public Security getSecurity() { return security; }
    public void setSecurity(Security security) { this.security = security; }
    
    public static class Security {
        private boolean enabled = true;
        private String secretKey = "default-secret";
        
        public boolean isEnabled() { return enabled; }
        public void setEnabled(boolean enabled) { this.enabled = enabled; }
        
        public String getSecretKey() { return secretKey; }
        public void setSecretKey(String secretKey) { this.secretKey = secretKey; }
    }
}

// Usage in properties:
// app.name=My Awesome App
// app.version=2.0.0
// app.security.enabled=true
// app.security.secret-key=my-secret-key

Environment-Specific Configuration

# application.yml (common configuration)
spring:
  application:
    name: my-app

---
# application-dev.yml (development)
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:h2:mem:devdb
  jpa:
    show-sql: true
  logging:
    level:
      com.example: DEBUG

---
# application-prod.yml (production)
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:postgresql://localhost:5432/proddb
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
  jpa:
    show-sql: false
  logging:
    level:
      com.example: WARN

Auto-Configuration

Spring Boot's auto-configuration automatically configures beans based on the classpath:

Custom Auto-Configuration

package com.example.autoconfigure;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

@AutoConfiguration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "myservice", name = "enabled", havingValue = "true", matchIfMissing = true)
    public MyService myService(MyServiceProperties properties) {
        return new MyService(properties.getApiKey(), properties.getTimeout());
    }
    
    @Bean
    @ConditionalOnBean(MyService.class)
    public MyServiceHealthIndicator myServiceHealthIndicator(MyService myService) {
        return new MyServiceHealthIndicator(myService);
    }
}

// Properties
@ConfigurationProperties(prefix = "myservice")
public class MyServiceProperties {
    private boolean enabled = true;
    private String apiKey;
    private int timeout = 5000;
    
    // Getters and setters
    public boolean isEnabled() { return enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }
    
    public String getApiKey() { return apiKey; }
    public void setApiKey(String apiKey) { this.apiKey = apiKey; }
    
    public int getTimeout() { return timeout; }
    public void setTimeout(int timeout) { this.timeout = timeout; }
}

Testing Spring Boot Applications

Spring Boot provides excellent testing support:

package com.example;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@ActiveProfiles("test")
class MyApplicationTests {
    
    @Test
    void contextLoads() {
        // This test ensures the application context loads successfully
    }
}

// Controller tests
package com.example.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.BDDMockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(UserController.class)
public class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @Test
    public void shouldCreateUser() throws Exception {
        User user = new User("John Doe", "[email protected]", 30);
        User savedUser = new User("John Doe", "[email protected]", 30);
        savedUser.setId(1L);
        
        given(userService.save(any(User.class))).willReturn(savedUser);
        
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(user)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(1))
                .andExpect(jsonPath("$.name").value("John Doe"))
                .andExpect(jsonPath("$.email").value("[email protected]"));
    }
    
    @Test
    public void shouldReturnUserById() throws Exception {
        User user = new User("John Doe", "[email protected]", 30);
        user.setId(1L);
        
        given(userService.findById(1L)).willReturn(Optional.of(user));
        
        mockMvc.perform(get("/api/users/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("John Doe"));
    }
}

// Repository tests
package com.example.repository;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;

@DataJpaTest
public class UserRepositoryTest {
    
    @Autowired
    private TestEntityManager entityManager;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    public void shouldFindUserByEmail() {
        // Given
        User user = new User("John Doe", "[email protected]", 30);
        entityManager.persistAndFlush(user);
        
        // When
        Optional<User> found = userRepository.findByEmail("[email protected]");
        
        // Then
        assertThat(found).isPresent();
        assertThat(found.get().getName()).isEqualTo("John Doe");
    }
    
    @Test
    public void shouldReturnEmptyWhenEmailNotFound() {
        Optional<User> found = userRepository.findByEmail("[email protected]");
        assertThat(found).isNotPresent();
    }
}

Production Features

Spring Boot includes many production-ready features:

Actuator Endpoints

package com.example.actuator;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class DatabaseHealthIndicator implements HealthIndicator {
    
    private final UserRepository userRepository;
    
    public DatabaseHealthIndicator(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Override
    public Health health() {
        try {
            long userCount = userRepository.count();
            return Health.up()
                    .withDetail("userCount", userCount)
                    .withDetail("status", "Database is accessible")
                    .build();
        } catch (Exception e) {
            return Health.down()
                    .withDetail("error", e.getMessage())
                    .build();
        }
    }
}

// Custom info endpoint
package com.example.actuator;

import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;

@Component
public class AppInfoContributor implements InfoContributor {
    
    @Override
    public void contribute(Info.Builder builder) {
        builder.withDetail("app", Map.of(
            "name", "My Spring Boot App",
            "description", "A sample Spring Boot application",
            "version", "1.0.0",
            "features", List.of("REST API", "JPA", "Security")
        ));
    }
}

Metrics and Monitoring

package com.example.metrics;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;

@Component
public class UserMetrics {
    
    private final Counter userCreatedCounter;
    private final Counter userDeletedCounter;
    
    public UserMetrics(MeterRegistry meterRegistry) {
        this.userCreatedCounter = Counter.builder("users.created")
                .description("Number of users created")
                .register(meterRegistry);
        
        this.userDeletedCounter = Counter.builder("users.deleted")
                .description("Number of users deleted")
                .register(meterRegistry);
    }
    
    public void incrementUserCreated() {
        userCreatedCounter.increment();
    }
    
    public void incrementUserDeleted() {
        userDeletedCounter.increment();
    }
}

// Updated service with metrics
@Service
public class UserService {
    
    private final UserRepository userRepository;
    private final UserMetrics userMetrics;
    
    public UserService(UserRepository userRepository, UserMetrics userMetrics) {
        this.userRepository = userRepository;
        this.userMetrics = userMetrics;
    }
    
    public User save(User user) {
        validateUser(user);
        User saved = userRepository.save(user);
        userMetrics.incrementUserCreated();
        return saved;
    }
    
    public void deleteById(Long id) {
        if (!userRepository.existsById(id)) {
            throw new UserNotFoundException("User not found: " + id);
        }
        userRepository.deleteById(id);
        userMetrics.incrementUserDeleted();
    }
    
    // Other methods...
}

Summary

Spring Boot revolutionizes Spring development:

Key Benefits:

  • Rapid Development: Get applications running quickly with minimal setup
  • Auto-Configuration: Intelligent defaults based on classpath scanning
  • Production-Ready: Built-in monitoring, metrics, and health checks
  • Microservices: Perfect foundation for microservices architecture
  • Ecosystem: Rich ecosystem of starters and third-party integrations

Core Features:

  • Starters: Dependency management made simple
  • Auto-Configuration: Convention over configuration
  • Embedded Servers: No need for external application servers
  • Configuration: Flexible property-based configuration
  • Testing: Comprehensive testing support with specialized annotations

Best Practices:

  • Use Starters: Leverage Spring Boot starters for functionality
  • Constructor Injection: Prefer constructor over field injection
  • Profile-Specific Config: Use profiles for different environments
  • Actuator: Enable monitoring and health checks
  • Testing: Write comprehensive tests at all layers

Production Considerations:

  • Externalized Config: Use environment variables and config servers
  • Monitoring: Implement comprehensive logging and metrics
  • Security: Follow security best practices
  • Performance: Monitor and optimize application performance
  • Deployment: Use container-based deployment strategies

Spring Boot is the de facto standard for building modern Java applications, providing a powerful yet simple foundation for enterprise development.