Java 26-Day Course - Day 24: Introduction to Spring Boot

Day 24: Introduction to Spring Boot

Spring Boot is the de facto standard framework for Java web application development. It lets you build applications quickly without complex configuration. Using a restaurant analogy, Spring is the kitchen equipment set, while Spring Boot is a turnkey kitchen with everything already set up.

Starting a Spring Boot Project

Generate a project via Spring Initializr (https://start.spring.io) or configure it manually.

// build.gradle.kts
/*
plugins {
    java
    id("org.springframework.boot") version "3.2.0"
    id("io.spring.dependency-management") version "1.1.4"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
    sourceCompatibility = JavaVersion.VERSION_17
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.test {
    useJUnitPlatform()
}
*/

// Main application class
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication // Auto-config + component scanning + config class
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
        // Embedded Tomcat server starts on port 8080
    }
}

IoC/DI (Inversion of Control and Dependency Injection)

Understanding Spring’s core principle. The Spring container manages object creation and lifecycle instead of the developer.

import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;

// Interface definition
interface GreetingService {
    String greet(String name);
}

// Implementation 1: Register as a Spring Bean with @Service
@Service
class KoreanGreetingService implements GreetingService {
    @Override
    public String greet(String name) {
        return "Hello, " + name + "!";
    }
}

// Implementation 2 (select with @Primary or @Qualifier when needed)
// @Service
// class EnglishGreetingService implements GreetingService {
//     @Override
//     public String greet(String name) {
//         return "Hello, " + name + "!";
//     }
// }

// Class that receives dependency injection
@Component
class WelcomeProcessor {
    private final GreetingService greetingService;

    // Constructor injection (recommended approach)
    // @Autowired can be omitted (when there's only one constructor)
    WelcomeProcessor(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    String processWelcome(String name) {
        String greeting = greetingService.greet(name);
        return "[Welcome] " + greeting;
    }
}

// Benefits of DI:
// 1. Loose coupling: easy to swap implementations
// 2. Testability: can inject mock objects
// 3. Code reuse: same bean used in multiple places

Simple Web Controller

Create a controller that handles HTTP requests.

import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import java.time.LocalDateTime;
import java.util.Map;

@RestController // Controller that returns JSON responses
@RequestMapping("/api") // Common path prefix
class HelloController {

    private final GreetingService greetingService;

    HelloController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    // GET /api/hello
    @GetMapping("/hello")
    Map<String, Object> hello() {
        return Map.of(
            "message", "Hello, Spring Boot!",
            "timestamp", LocalDateTime.now().toString()
        );
    }

    // GET /api/hello/Alice
    @GetMapping("/hello/{name}")
    Map<String, String> helloName(@PathVariable String name) {
        return Map.of(
            "greeting", greetingService.greet(name)
        );
    }

    // GET /api/search?keyword=java&page=1
    @GetMapping("/search")
    Map<String, Object> search(
            @RequestParam String keyword,
            @RequestParam(defaultValue = "1") int page) {
        return Map.of(
            "keyword", keyword,
            "page", page,
            "results", "Search results for '" + keyword + "' (page " + page + ")"
        );
    }

    // POST /api/echo
    @PostMapping("/echo")
    ResponseEntity<Map<String, Object>> echo(@RequestBody Map<String, Object> body) {
        return ResponseEntity.ok(Map.of(
            "received", body,
            "echo", true,
            "timestamp", LocalDateTime.now().toString()
        ));
    }
}

application.properties Configuration

The Spring Boot application configuration file.

// src/main/resources/application.properties
/*
# Server settings
server.port=8080
server.servlet.context-path=/

# Logging settings
logging.level.root=INFO
logging.level.com.example=DEBUG

# JSON settings
spring.jackson.serialization.indent-output=true
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

# Database (H2 in-memory)
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

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

// application.yml format (alternative):
/*
server:
  port: 8080

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver

logging:
  level:
    root: INFO
    com.example: DEBUG
*/

// Profile-specific settings:
// application-dev.properties  (development)
// application-prod.properties (production)
// Activation: spring.profiles.active=dev

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

@Component
class AppConfig {
    @Value("${server.port:8080}")
    private int port;

    @Value("${app.name:MyApp}")
    private String appName;

    void printConfig() {
        System.out.println("Port: " + port);
        System.out.println("App name: " + appName);
    }
}

Today’s Exercises

  1. Profile API: Create a /api/profile endpoint. GET returns profile info (name, email, bio), and POST updates the profile. Store data in memory (Map).

  2. Currency Exchange API: Create a controller that handles GET requests in the form /api/exchange?from=USD&to=KRW&amount=100. Hardcode exchange rate data in a Map, and return an appropriate error response for unsupported currencies.

  3. DI Practice: Create a NotificationService interface with EmailNotification and SmsNotification implementations, and write a controller that uses @Qualifier to inject the desired implementation.

Was this article helpful?