Loading Now

Spring Boot in a Nutshell

spring-boot-nutshell

Spring Boot in a Nutshell

Spring Boot builds on Spring to let you create production-grade applications with minimal configuration. Annotations drive most of the functionality – component scanning, dependency injection, configuration, web endpoints, persistence, security, caching, scheduling, and testing. Read more about them in this blog.


Application Bootstrap & Configuration

@SpringBootApplication

What it is: Convenience annotation that combines @Configuration, @EnableAutoConfiguration, and @ComponentScan.
Use when: Bootstrapping your application.

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

@SpringBootApplication
public class DcepApplication {
  public static void main(String[] args) {
    SpringApplication.run(DcepApplication.class, args);
  }
}

@Configuration+@Bean

What they do: Define configuration classes and factory methods for Spring-managed beans.
Use when: You need fine-grained control over bean creation or third‑party library wiring.

import org.springframework.context.annotation.*;

@Configuration
public class AppConfig {

  @Bean
  @Primary // preferred bean if multiple candidates exist
  public ObjectMapper objectMapper() {
    return new ObjectMapper().findAndRegisterModules();
  }
}

@Value and @ConfigurationProperties

What they do: Bind properties to fields.
Use when: Injecting configuration values (URLs, timeouts, credentials).

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ExternalServiceProps {
  @Value("${service.endpoint.url}")
  private String endpointUrl;
}

// Prefer strongly-typed binding:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "service")
public class ServiceProperties {
  private String endpointUrl;
  private int timeout;
  // getters/setters
}

@Profile and Conditional Annotations

What they do: Activate beans based on environment or conditions.
Use when: Different behavior for dev, test, prod.

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

@Service
@Profile("dev")
public class DevEmailService implements EmailService { /* ... */ }
import org.springframework.boot.autoconfigure.condition.*;

@Configuration
public class FeatureToggleConfig {

  @Bean
  @ConditionalOnProperty(name = "feature.x.enabled", havingValue = "true")
  public FeatureX featureX() { return new FeatureX(); }

  @Bean
  @ConditionalOnMissingBean
  public DefaultFeature defaultFeature() { return new DefaultFeature(); }
}

Component Model & Dependency Injection

@Component, @Service, @Repository, @Controller, @RestController

What they do: Mark classes for component scanning and define their roles.
Use when: Structuring layers and enabling DI.

import org.springframework.stereotype.*;

@Service
public class PaymentService { /* business logic */ }

@Repository
public interface PaymentRepository extends JpaRepository<Payment, Long> { }

@RestController
@RequestMapping("/api/payments")
public class PaymentController { /* endpoints */ }

@RestController = @Controller + @ResponseBody for JSON responses.


@Autowired, @Qualifier, @Primary

What they do: Inject dependencies and disambiguate between multiple candidates.
Use when: You have multiple beans of same type.

@Service
public class ReportService {
  private final ReportGenerator generator;

  @Autowired
  private final PdfService pdfService;

  public ReportService(@Qualifier("pdfReportGenerator") ReportGenerator generator) {
    this.generator = generator;
  }
}

Web & REST (Spring MVC)

@RequestMapping,@GetMapping,@PostMapping,@PutMapping, @DeleteMapping

What they do: Map HTTP requests to handler methods.
Use when: Defining REST endpoints.

@RestController
@RequestMapping("/api/users")
public class UserController {

  @GetMapping("/{id}")
  public UserDto get(@PathVariable Long id) { /*...*/ }

  @PostMapping
  @ResponseStatus(HttpStatus.CREATED)
  public UserDto create(@Valid @RequestBody CreateUserRequest req) { /*...*/ }
}

@PathVariable, @RequestParam, @RequestBody, @ResponseStatus

What they do: Bind parts of the request to method parameters and control response status.

@GetMapping("/list/{type}")
public List<UserDto> list(@RequestParam(defaultValue = "0") int page,
                          @RequestParam(defaultValue = "20") int size,
                          @PathVariable(value="type") String type) { /*...*/ }

@ControllerAdvice + @ExceptionHandler

What they do: Centralized exception handling across controllers.
Use when: Returning uniform error format.

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

@ControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler(EntityNotFoundException.class)
  @ResponseStatus(HttpStatus.NOT_FOUND)
  @ResponseBody
  public ErrorResponse handleNotFound(EntityNotFoundException ex) {
    return new ErrorResponse("NOT_FOUND", ex.getMessage());
  }
}

@CrossOrigin

What it does: Enable CORS on controllers or methods.
Use when: Frontend hosted on a different origin.

@CrossOrigin(origins = "https://app.example.com")
@GetMapping("/public")
public InfoDto publicInfo() { /*...*/ }

Validation (Bean Validation / Jakarta Validation)

@Valid and constraint annotations (@NotNull, @Size, @Email, etc.)

What they do: Validate request payloads and beans.
Use when: Input validation at the API boundary.

import jakarta.validation.constraints.*;

public record CreateUserRequest(
  @NotBlank String username,
  @Email String email,
  @Size(min = 8) String password
) {}

@PostMapping("/users")
public UserDto create(@Valid @RequestBody CreateUserRequest req) { /*...*/ }

Data & Persistence (Spring Data JPA)

JPA Entity Annotations: @Entity, @Table, @Id, @GeneratedValue, @Column

What they do: Map classes to database tables.
Use when: Persisting domain models.

import jakarta.persistence.*;

@Entity
@Table(name = "payments")
public class Payment {
  @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(nullable = false)
  private Long amount;

  // relationships, getters, setters
}

Relationship Annotations: @ManyToOne, @OneToMany, @OneToOne, @ManyToMany, @JoinColumn

What they do: Define associations between entities.

@Entity
public class Order {
  @Id @GeneratedValue private Long id;

  @ManyToOne
  @JoinColumn(name = "customer_id")
  private Customer customer;

  @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
  private List<OrderLine> lines = new ArrayList<>();
}

Spring Data Repositories: @Repository, CrudRepository/JpaRepository

What they do: Auto-implement common CRUD operations; query derivation by method name.

@Repository
public interface PaymentRepository extends JpaRepository<Payment, Long> {
  List<Payment> findByAmountGreaterThan(long minAmount);
}

@Transactional

What it does: Demarcate transactional boundaries.
Use when: Methods that read/write the database.

import org.springframework.transaction.annotation.Transactional;

@Service
public class PaymentService {

  @Transactional
  public Payment createPayment(Payment p) { return repo.save(p); }

  @Transactional(readOnly = true)
  public Optional<Payment> find(Long id) { return repo.findById(id); }
}

Caching

@EnableCaching, @Cacheable, @CachePut, @CacheEvict

What they do: Enable cache abstraction and control caching behavior.
Use when: Improving performance of repeated reads.

import org.springframework.cache.annotation.*;

@Configuration
@EnableCaching
public class CacheConfig { /* cache manager setup if needed */ }

@Service
public class ProductService {

  @Cacheable(value = "productById", key = "#id")
  public Product getProduct(Long id) { return repo.findById(id).orElseThrow(); }

  @CachePut(value = "productById", key = "#product.id")
  public Product update(Product product) { return repo.save(product); }

  @CacheEvict(value = "productById", key = "#id")
  public void delete(Long id) { repo.deleteById(id); }
}

Asynchronous & Scheduling

@EnableAsync + @Async

What they do: Run methods in a separate thread pool.
Use when: Sending emails, calling external APIs, heavy computations that shouldn’t block.

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;

@Configuration
@EnableAsync
public class AsyncConfig { }

@Service
public class NotificationService {
  @Async
  public CompletableFuture<Void> sendEmailAsync(String to) { /*...*/ return CompletableFuture.completedFuture(null); }
}

@EnableScheduling + @Scheduled

What they do: Run methods on a schedule (cron, fixed rate, fixed delay).
Use when: Cleanups, sync jobs, reporting.

import org.springframework.scheduling.annotation.*;

@Configuration
@EnableScheduling
public class SchedulerConfig { }

@Component
public class DailyJob {

  @Scheduled(cron = "0 0 2 * * *") // every day at 02:00
  public void runNightlyCleanup() { /*...*/ }
}

Security (Spring Security)

@EnableWebSecurity and Method Security (@EnableMethodSecurity / @PreAuthorize)

What they do: Configure web security and guard methods with role/permission checks.

import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableWebSecurity
public class SecurityConfig { /* HttpSecurity configuration */ }
import org.springframework.security.access.prepost.PreAuthorize;

@Service
public class AdminService {
  @PreAuthorize("hasRole('ADMIN')")
  public void performAdminTask() { /*...*/ }
}

In Spring Security 6+, use @EnableMethodSecurity (supersedes @EnableGlobalMethodSecurity).


Observability

Spring Boot Actuator is largely auto-configured, but you’ll often use:

@Endpoint (advanced), or rely on auto-configured endpoints

What it does: Create custom actuator endpoints.
Use when: Exposing health or diagnostics specific to your domain.

import org.springframework.boot.actuate.endpoint.annotation.*;

@Endpoint(id = "customInfo")
@Component
public class CustomInfoEndpoint {
  @ReadOperation
  public Map<String, Object> info() { return Map.of("build", "v1.2.3"); }
}

Testing

@SpringBootTest

What it does: Load the full application context for integration tests.
Use when: End-to-end or component integration testing.

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

@SpringBootTest
class AppIntegrationTest {
  @Test void contextLoads() { }
}

Slice Tests: @WebMvcTest, @DataJpaTest, @MockBean

What they do: Load a focused part of the context for fast tests.

import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;

@WebMvcTest(UserController.class)
class UserControllerTest {
  @MockBean private UserService userService;
  // use MockMvc to test endpoints
}
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

@DataJpaTest
class PaymentRepositoryTest {
  // repository tests with in-memory DB
}

Day-to-Day Best Practices

  1. Keep Controllers Thin: Use @Service for business logic; @RestController only wires I/O.
  2. Validate Inputs: Use @Valid + constraint annotations in request DTOs.
  3. Centralize Errors: @ControllerAdvice + @ExceptionHandler for consistent API responses.
  4. Transactions at Service Layer: Prefer @Transactional on service methods, not repositories.
  5. Use ConfigurationProperties: Typed config beats scattered @Value strings.
  6. Guard with Profiles: Use @Profile to separate dev/test/prod bean behavior.
  7. Cache Read-Heavy Methods: @Cacheable for hot lookups; remember eviction on writes.
  8. Schedule & Async Thoughtfully: Always consider thread pools and retries for @Async and @Scheduled.
  9. Secure at Method Level: @PreAuthorize keeps services safe even if endpoints change.
  10. Write Focused Tests: Use slice testing (@WebMvcTest, @DataJpaTest) for speed; full @SpringBootTest sparingly.

Common Pitfalls & How to Avoid Them

  • Bean Ambiguity: Provide @Qualifier or @Primary when multiple beans of the same type exist.
  • Lazy CORS Config: Don’t use @CrossOrigin("*") in production; prefer restricted origins and a global CORS config.
  • Transaction Misuse: @Transactional on read-only queries improves performance (hint to Hibernate).
  • Validation Gaps: Put @Valid on nested DTOs too; ensure validation dependencies are included.
  • Profile Leakage: Ensure spring.profiles.active is set correctly in each environment.
  • Scheduling Time Zones: Cron defaults to server time; set zone = "Asia/Kolkata" (or your region) explicitly if needed.

Handy Cheat Sheet (What to Reach For)

  • Bootstrapping → @SpringBootApplication
  • Beans & Config → @Configuration, @Bean, @ConfigurationProperties
  • DI & Components → @Service, @Repository, @RestController, @Autowired, @Qualifier
  • Web MVC → @GetMapping, @PostMapping, @PathVariable, @RequestParam, @RequestBody, @ControllerAdvice
  • Validation → @Valid, @NotNull, @Size, @Email
  • Persistence → @Entity, @Id, @GeneratedValue, @Transactional
  • Caching → @EnableCaching, @Cacheable, @CacheEvict, @CachePut
  • Async & Scheduling → @EnableAsync, @Async, @EnableScheduling, @Scheduled
  • Security → @EnableWebSecurity, @EnableMethodSecurity, @PreAuthorize
  • Testing → @SpringBootTest, @WebMvcTest, @DataJpaTest, @MockBean

Post Comment