java5 min read

Spring Boot Tutorial: Learn Microservices from Scratch (2026)

Spring Boot Tutorial: Learn Microservices from Scratch (2026)

Published:  |  Category: Java  |  Reading time: ~15 min
Spring Boot Tutorial: Learn Microservices from Scratch (2026)

Spring Boot changed the way Java developers build production services. Auto-configuration, embedded servers, and production-ready metrics let you go from zero to a deployed REST API in minutes. After shipping several Spring Boot services handling millions of requests daily, I can say its opinionated defaults save enormous configuration effort while remaining customizable when you need something different.

This guide covers what matters for real Spring Boot development: structuring applications, configuring properties, building REST endpoints, adding observability, and deploying with confidence. Each section draws from mistakes I have made and patterns I have seen work across teams.

Project Structure and Auto-Configuration

Spring Boot's auto-configuration inspects classpath dependencies and sensible property defaults to configure beans automatically. The @SpringBootApplication annotation combines @Configuration, @EnableAutoConfiguration, and @ComponentScan into one convenient annotation. Place your main class in the root package above all other packages to ensure component scanning covers every class.

Organize code by feature — delivery, payment, notification — rather than by layer. When each feature owns its controllers, services, and repositories, navigating the codebase becomes intuitive and refactoring stays localized.

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

// Feature-based package: com.example.orders.delivery
@RestController
@RequestMapping("/api/deliveries")
public class DeliveryController { ... }

Configuration Management

Externalize everything that changes between environments. Spring Boot reads application.properties or application.yml from classpath, then overrides with profile-specific files (application-prod.yml) and finally environment variables. Use @ConfigurationProperties to bind typed, validated POJOs to configuration prefixes.

Spring Cloud Config or HashiCorp Vault manage secrets for distributed systems. Never hardcode database passwords or API keys. Use ${variable} placeholders with sensible defaults to avoid startup failures when properties are missing.

@ConfigurationProperties(prefix = "app.payment")
public record PaymentProperties(
    String gatewayUrl,
    Duration timeout,
    @Max(3) int maxRetries
) {}

// application.yml
app:
  payment:
    gateway-url: https://api.stripe.com
    timeout: 5s
    max-retries: 3

Building REST APIs

Spring MVC on top of Spring Boot provides annotations for every HTTP concern. Use @RestController for JSON endpoints, @RequestMapping for class-level URL mapping, and @Valid with jakarta.validation for request validation. Return ResponseEntity for fine-grained control over HTTP status codes and headers.

Error handling with @ControllerAdvice centralizes exception-to-response mapping. Define a standard error response body with timestamp, status, message, and trace ID. This consistency saves client developers hours of debugging.

@PostMapping
public ResponseEntity createOrder(
        @Valid @RequestBody CreateOrderRequest request,
        UriComponentsBuilder uriBuilder) {
    OrderResponse created = orderService.create(request);
    URI location = uriBuilder.path("/api/orders/{id}")
        .build(created.id());
    return ResponseEntity.created(location).body(created);
}

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ValidationException.class)
    public ProblemDetail handle(ValidationException ex) {
        return ProblemDetail.forStatusAndDetail(
            HttpStatus.BAD_REQUEST, ex.getMessage());
    }
}

Data Access with Spring Data

Spring Data JPA eliminates boilerplate data-access code. Define an interface extending JpaRepository and get CRUD operations, pagination, and derived query methods automatically. Use @Query for custom JPQL or native SQL when the method naming convention cannot express the logic.

Enable auditing with @EnableJpaAuditing to automatically populate createdAt and updatedAt fields. For read-heavy workloads, consider adding Redis or Spring Data JDBC as a simpler alternative to JPA's complexity.

public interface OrderRepository extends JpaRepository {
    Page findByCustomerId(Long customerId, Pageable pageable);
    
    @Query("SELECT o FROM Order o WHERE o.total > :min AND o.status = :status")
    List findHighValueOrders(@Param("min") BigDecimal min, 
                                    @Param("status") OrderStatus status);
}

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Order {
    @CreatedDate
    private LocalDateTime createdAt;
    @LastModifiedDate
    private LocalDateTime updatedAt;
}

Observability: Metrics, Tracing, Logging

Spring Boot Actuator exposes health checks, metrics, and environment info over HTTP. Combine with Micrometer to export metrics to Prometheus, Datadog, or New Relic. Distributed tracing with Micrometer Tracing integrates with Zipkin or Jaeger to follow requests across service boundaries.

Structured logging with Logback outputs JSON logs that log aggregators parse efficiently. Include trace IDs in every log line so you can correlate logs with traces during incident response.

// application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus
  tracing:
    sampling:
      probability: 0.1


    

Testing Spring Boot Applications

@SpringBootTest loads the full application context for integration tests. Slice tests like @WebMvcTest, @DataJpaTest, and @JsonTest load only relevant beans — they run faster and isolate the component under test. Use Testcontainers for database and message-broker integration tests instead of H2 in-memory databases, which behave differently from production databases.

WireMock stubs external HTTP services so tests never depend on network availability. Structure tests with Arrange-Act-Assert and use @DirtiesContext when tests mutate shared state.

@WebMvcTest(OrderController.class)
class OrderControllerTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private OrderService orderService;

    @Test
    void returnsOrdersForCustomer() throws Exception {
        given(orderService.findByCustomerId(1L))
            .willReturn(List.of(new OrderResponse(...)));
        mockMvc.perform(get("/api/orders?customerId=1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.length()").value(1));
    }
}

Frequently Asked Questions

How do I handle database migrations in Spring Boot?

Use Flyway or Liquibase. Place migration scripts in src/main/resources/db/migration and Spring Boot applies them automatically on startup. Never modify an applied migration — create a new one instead.

What is the difference between @Component, @Service, and @Repository?

All three register beans in the application context. @Service and @Repository are specializations of @Component that add semantic meaning. @Repository also enables persistence exception translation. Use them consistently to communicate intent.

Should I use Spring Boot 2.x or 3.x for new projects?

Start with Spring Boot 3.x — it is built on Spring Framework 6, requires Java 17+, and supports virtual threads, native images, and Jakarta EE. Spring Boot 2.x is in maintenance mode and should only be used for legacy compatibility.

How do I secure a Spring Boot REST API?

Use Spring Security with OAuth2 resource server for token-based authentication. Configure CORS, CSRF (disabled for stateless APIs), and rate limiting. For internal services, consider mutual TLS as an alternative to token-based auth.

Originally published on Ayodhyyya. Last updated June 1, 2026.