Skip to main content
Technology & EngineeringJava Spring230 lines

Spring Boot Basics

Core Spring Boot concepts including auto-configuration, starters, dependency injection, and application lifecycle

Quick Summary27 lines
You are an expert in Spring Boot fundamentals for building production-ready Java applications. You value clean layering, explicit dependency injection, and configuration that is environment-aware from the start rather than retrofitted before the first deploy.

## Key Points

- `@Configuration` — marks the class as a source of bean definitions
- `@EnableAutoConfiguration` — triggers Spring Boot's auto-configuration mechanism
- `@ComponentScan` — scans for components in the current package and sub-packages
- **Use constructor injection** over field injection — it makes dependencies explicit, supports immutability, and simplifies testing.
- **Externalize configuration** — never hardcode URLs, credentials, or environment-specific values. Use `application.yml` with `${ENV_VAR}` placeholders.
- **Layer your architecture** — separate controllers, services, and repositories. Controllers handle HTTP; services hold business logic; repositories manage persistence.
- **Use DTOs** — do not expose JPA entities directly in API responses. Map between entities and DTOs to decouple your API contract from your data model.
- **Enable graceful shutdown** — set `server.shutdown=graceful` so in-flight requests complete before the application stops.
- **Use `@ConfigurationProperties`** for typed, validated configuration instead of scattered `@Value` annotations.
- **Circular dependencies** — two beans depending on each other cause startup failure. Redesign to break the cycle or use `@Lazy` as a last resort.
- **Blocking in reactive stacks** — mixing Spring MVC blocking calls into a WebFlux application defeats the purpose of reactive programming.
- **Overly broad component scanning** — scanning too many packages slows startup. Keep `@SpringBootApplication` at the root of your package hierarchy.

## Quick Example

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
```
skilldb get java-spring-skills/Spring Boot BasicsFull skill: 230 lines
Paste into your CLAUDE.md or agent config

Spring Boot Basics — Java/Spring Boot

You are an expert in Spring Boot fundamentals for building production-ready Java applications. You value clean layering, explicit dependency injection, and configuration that is environment-aware from the start rather than retrofitted before the first deploy.

Core Philosophy

Spring Boot's power comes from convention over configuration, but convention is not the same as magic. Every auto-configured bean has a reason, a condition under which it activates, and a way to override it. Understanding what Spring Boot does behind the scenes -- which beans it creates, which properties it reads, which starters pull in which transitive dependencies -- is the difference between productive development and hours of debugging mysterious behavior. The --debug flag and the auto-configuration report are not advanced tools; they are essential everyday instruments.

The layered architecture of controller, service, and repository is not bureaucratic ceremony. It exists because mixing HTTP concerns with business logic with persistence logic produces code that is hard to test, hard to refactor, and hard to reason about. A controller should translate HTTP into method calls and method results back into HTTP. A service should express business rules without knowing whether it was invoked by a web request, a queue consumer, or a test. A repository should handle persistence without embedding business decisions. When these boundaries blur, the codebase becomes brittle in ways that are expensive to fix.

Configuration should be externalized from the first commit, not right before the first deploy. Hardcoded values become technical debt the moment the application needs to run in a second environment. Spring Boot's property resolution, profile system, and @ConfigurationProperties mechanism make it straightforward to build applications that adapt to their environment without code changes. Treating configuration as an afterthought leads to the single most common category of production incidents: deploying with the wrong settings.

Overview

Spring Boot simplifies Java application development by providing opinionated defaults, auto-configuration, and embedded servers. It eliminates most boilerplate XML configuration and lets developers focus on business logic. Built on top of the Spring Framework, it uses convention over configuration to get applications running quickly.

Core Concepts

Auto-Configuration

Spring Boot automatically configures beans based on the classpath, existing beans, and properties. The @SpringBootApplication annotation combines three key annotations:

  • @Configuration — marks the class as a source of bean definitions
  • @EnableAutoConfiguration — triggers Spring Boot's auto-configuration mechanism
  • @ComponentScan — scans for components in the current package and sub-packages

Dependency Injection

Spring manages object creation and wiring through its IoC (Inversion of Control) container. Beans are created and injected based on type, qualifier, or name.

@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final PaymentGateway paymentGateway;

    // Constructor injection — preferred approach
    public OrderService(OrderRepository orderRepository, PaymentGateway paymentGateway) {
        this.orderRepository = orderRepository;
        this.paymentGateway = paymentGateway;
    }

    public Order placeOrder(OrderRequest request) {
        Order order = Order.from(request);
        paymentGateway.charge(order.getTotal());
        return orderRepository.save(order);
    }
}

Starters

Starters are curated dependency descriptors. Instead of hunting for compatible library versions, include a single starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Common starters: spring-boot-starter-web, spring-boot-starter-data-jpa, spring-boot-starter-security, spring-boot-starter-test, spring-boot-starter-actuator.

Application Properties

Configure behavior through application.yml or application.properties:

server:
  port: 8080
  servlet:
    context-path: /api

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/mydb
    username: ${DB_USER}
    password: ${DB_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false

logging:
  level:
    com.example: DEBUG
    org.springframework.web: INFO

Profiles

Profiles allow environment-specific configuration:

# application-dev.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb
  jpa:
    hibernate:
      ddl-auto: create-drop

Activate with --spring.profiles.active=dev or the SPRING_PROFILES_ACTIVE environment variable.

Implementation Patterns

REST Controller

@RestController
@RequestMapping("/api/v1/products")
public class ProductController {

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping
    public ResponseEntity<List<ProductDTO>> listProducts(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size) {
        List<ProductDTO> products = productService.findAll(page, size);
        return ResponseEntity.ok(products);
    }

    @GetMapping("/{id}")
    public ResponseEntity<ProductDTO> getProduct(@PathVariable Long id) {
        return productService.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public ResponseEntity<ProductDTO> createProduct(@Valid @RequestBody CreateProductRequest request) {
        ProductDTO created = productService.create(request);
        URI location = URI.create("/api/v1/products/" + created.getId());
        return ResponseEntity.created(location).body(created);
    }
}

Exception Handling

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
        ErrorResponse error = new ErrorResponse("NOT_FOUND", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult().getFieldErrors().stream()
                .map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
                .toList();
        ErrorResponse error = new ErrorResponse("VALIDATION_ERROR", String.join("; ", errors));
        return ResponseEntity.badRequest().body(error);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
        ErrorResponse error = new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred");
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

Configuration Classes

@Configuration
public class AppConfig {

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder
                .setConnectTimeout(Duration.ofSeconds(5))
                .setReadTimeout(Duration.ofSeconds(10))
                .build();
    }

    @Bean
    @ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("products", "users");
    }
}

Best Practices

  • Use constructor injection over field injection — it makes dependencies explicit, supports immutability, and simplifies testing.
  • Externalize configuration — never hardcode URLs, credentials, or environment-specific values. Use application.yml with ${ENV_VAR} placeholders.
  • Layer your architecture — separate controllers, services, and repositories. Controllers handle HTTP; services hold business logic; repositories manage persistence.
  • Use DTOs — do not expose JPA entities directly in API responses. Map between entities and DTOs to decouple your API contract from your data model.
  • Enable graceful shutdown — set server.shutdown=graceful so in-flight requests complete before the application stops.
  • Use @ConfigurationProperties for typed, validated configuration instead of scattered @Value annotations.

Common Pitfalls

  • Circular dependencies — two beans depending on each other cause startup failure. Redesign to break the cycle or use @Lazy as a last resort.
  • Blocking in reactive stacks — mixing Spring MVC blocking calls into a WebFlux application defeats the purpose of reactive programming.
  • Overly broad component scanning — scanning too many packages slows startup. Keep @SpringBootApplication at the root of your package hierarchy.
  • Ignoring startup failures — auto-configuration failures can be silent. Check startup logs and use --debug to see which auto-configurations were applied or skipped.
  • Not using profiles — deploying with ddl-auto=create-drop in production because dev defaults leaked through.

Anti-Patterns

  • The God service — a single service class that handles authentication, validation, persistence, notification, and reporting because "it is all related to orders." Break services along single-responsibility lines so each class can be tested, understood, and modified independently.

  • Field injection everywhere — using @Autowired on private fields because it requires fewer keystrokes than constructor injection. Field injection hides dependencies, prevents immutability, and makes it impossible to construct the object in a test without reflection or a Spring context. Constructor injection is the default for a reason.

  • Profile-less development — running the same application.yml in development and production, relying on environment variables to override dangerous defaults. Without profiles, a missing environment variable means production uses the development database URL. Define explicit profiles and fail fast if the active profile does not match the deployment target.

  • Starter dependency sprawl — adding every starter that might be useful and never pruning the dependency list. Each starter brings auto-configuration, transitive dependencies, and potential classpath conflicts. Include only what the application actively uses and remove starters that were added speculatively.

  • Exception swallowing in controllers — catching Exception broadly and returning a generic 200 response so the client "does not see errors." This hides bugs, breaks monitoring, and makes debugging impossible. Use @RestControllerAdvice to map exceptions to appropriate HTTP status codes and structured error responses.

Install this skill directly: skilldb add java-spring-skills

Get CLI access →