Week 2 | Lesson 9

Spring Boot Application & Controllers

Spring Boot Introduction, Frameworks, MVC Pattern, Application Structure, Beans & DI, REST API, Controllers



© 2026 by Monika Protivová

Kotlin Applications

Kotlin Applications

Java is one of the most popular programming languages for WEB APPLICATION DEVELOPMENT, and since Java and Kotlin are interoperable, Kotlin can be used with Java applications frameworks.

Java/Kotlin is so popular, because of its core features like platform independence, automatic memory management, garbage collection, and security, which makes it suitable for building small or large scale enterprise applications that can run on any platform.

A very common Java application is for developing so-called APPLICATION BACKENDS,
which are the server-side applications that power client-side applications, such as web applications, mobile apps, and other web services, by providing business logic, database access, user authentication, and other services.

Client-side applications are usually developed using JavaScript, HTML,
and CSS and are also called APPLICATION FRONTENDS.

Frameworks

Framework

When we talk about frameworks, we usually mean a collection of libraries and tools that help developers build applications in a faster and more productive way.

For example, a framework can provide a set of libraries for ...

  • data access (databases)
  • security (user authorization and authentication)
  • network communication
  • configuration management
  • cloud integration
  • monitoring and logging
  • testing

... and other common tasks, so that developers don't have to write them from scratch.

Popular Frameworks

There are some Kotlin-specific frameworks, but often times, Kotlin developers use Java frameworks, as Java and Kotlin are interoperable and can share ecosystem.

Java

  • Spring Framework
  • Jakarta EE (formerly Java EE)
  • Hibernate
  • Play Framework
  • Apache Struts
  • Quarkus
  • MicroProfile

Kotlin specific

  • Ktor

Spring Framework

Spring Framework

Spring Framework is a whole ecosystem of tools and libraries for Java application development.

The ecosystem includes Spring Boot, Spring Cloud, Spring Data, Spring Security, Spring Integration, Spring Batch, and many other projects, that help developers deal with common application development tasks, such as data access, security, messaging, and more.

Spring Framework relies on the Dependency Injection (DI) design pattern to achieve Inversion of Control (IoC). This means, the responsibility of creating and managing the objects is shifted from the application code to the Spring Framework.

It is open-source.

Spring.io

Spring Boot

Spring Boot is a project built on top of the Spring Framework. It was designed to simplify the bootstrapping and development of new Spring applications.

Here are some of the features of Spring Boot ...

  • It can be run as a standalone application on variety of platforms.
    There is no need for separate web container. It bundles Tomcat, Jetty, or Undertow directly.
  • Externalized configuration and auto-configuration support, profiles
  • Production-ready features, logging, such as metrics, health checks
  • Web development support, REST, JSON, ...
  • Security features and configuration
  • Caching support
  • Messaging support
  • Testing support

Why Spring Boot?

Building Web Applications in Kotlin

You've already built applications with dependency injection - what's next?

In Assignment 08, you created a well-architected application with:

  • Service interfaces - Separated business logic
  • Constructor injection - Managed dependencies manually
  • SOLID principles - Clean, maintainable code

But what if you need to build a web application?

New Challenges

  • How do you handle HTTP requests and responses?
  • Who creates and manages all your service instances?
  • How do you connect to databases?
  • How do you handle JSON serialization?
  • How do you secure your application?
Building all of this from scratch would take weeks. This is where frameworks come in.

Frameworks Solve Common Problems

Spring Boot is a framework that handles the repetitive work so you can focus on business logic

What Spring Boot Provides:

  • Automatic Dependency Injection
    No more manual wiring - Spring Boot creates and injects your services automatically
  • HTTP Server
    Built-in web server to handle requests and responses
  • JSON Serialization
    Automatic conversion between Kotlin objects and JSON
  • Database Integration
    Simple database access with minimal configuration
  • Security
    Authentication and authorization out of the box
Spring Boot lets you write business logic using the same patterns you learned in Assignment 08, but handles all the web application infrastructure for you.

From Manual DI to Spring Boot

See how Spring Boot simplifies the code you've already written

Manual Dependency Injection (Assignment 08):

// Define interface interface TaskService { fun getTasks(): List<String> } // Implement service class TaskServiceImpl : TaskService { override fun getTasks() = listOf("Task A", "Task B", "Task C") } // Create and inject manually in main() fun main() { val taskService: TaskService = TaskServiceImpl() val taskManager = TaskManagerApp(taskService) taskManager.start() }

With Spring Boot:

// Add @Service annotation @Service class TaskService { fun getTasks() = listOf("Task A", "Task B", "Task C") } // Spring Boot creates and injects automatically! @RestController class TaskController( private val taskService: TaskService // Injected by Spring Boot ) { @GetMapping("/tasks") fun getTasks() = taskService.getTasks() } // No manual wiring needed - Spring Boot handles it
Same patterns, less boilerplate. Spring Boot automates what you did manually in Assignment 08.

Spring Boot Application

Spring Boot Application

Spring Boot application consists of a main class and a set of beans. It is independently deployable, and it can be run as a standalone application.

Spring Boot is annotation-driven, which means annotations are used to manage configurations and define so-called beans.

Bean is an object that is managed by the Spring IoC container.

Controller, Service and Repository are actually all different types of beans.

Spring Boot Application

A basic Spring Boot application main class will look like this

import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @SpringBootApplication class HelloApplication fun main(args: Array<String>) { runApplication<HelloApplication>(*args) }

The @SpringBootApplication annotation is used to mark the class Application as the main class of the Spring Boot application.

The main method is used to start the application by calling the runApplication method with the main class and argument.

The application class may optionally contain other annotations, such as @ComponentScan, @EnableAutoConfiguration, and @Configuration, which are used to further configure the application.

Beans & Dependency Injection

Spring Boot Beans & Dependency Injection

Spring Boot automates the dependency injection you did manually in Assignment 08

Remember creating service instances manually?

// Assignment 08 - Manual approach interface TaskService { fun getTasks(): List<String> } class TaskServiceImpl : TaskService { override fun getTasks() = listOf("Task A", "Task B") } fun main() { val taskService: TaskService = TaskServiceImpl() // Manual creation val app = TaskManagerApp(taskService) // Manual injection }

With Spring Boot, just add @Service annotation:

// Spring Boot - Automatic approach @Service class TaskService { fun getTasks() = listOf("Task A", "Task B") } @RestController class TaskController( private val taskService: TaskService // Spring Boot injects automatically! ) { @GetMapping("/tasks") fun getTasks() = taskService.getTasks() }
Spring Boot creates beans (managed objects) and injects them automatically. You focus on business logic, Spring Boot handles the wiring.

Bean

Bean is an object that is managed by the Spring IoC container. By defining a bean, we are telling the Spring container to create an instance of the bean and manage its lifecycle.

To define a bean, you simply need to annotate a method with the @Bean annotation.

User Bean defined in UserComponent. The bean is then available for dependency injection in other parts of the application.
@Component // or @Configuration class UserComponent { @Bean fun user(): User { return User("Monika") } }
Injection by constructor (preferred)
@Component class HigherLevelComponent( private val user: User ) { fun doSomething() { // do something with user } }
Injection by @Autowired annotation
@Component class HigherLevelComponent { @Autowired private lateinit var user: User fun doSomething() { // do something with user } }

Controller

Controllers beans that are conventionally responsible for handling requests and returning the responses, for example, RESTful services.

Annotating a class with @Controller or @RestController makes it a controller that handles the HTTP requests and returns the HTTP responses.

In this example, the HelloController class handles the HTTP GET request to the /hello endpoint and returns the string "Hello World!".

import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api") class HelloController( private val helloService: HelloService ) { @RequestMapping("/hello") fun getHelloWorld( @RequestParam(value = "name") name: String, @RequestParam(value = "locale") locale: String ): String { return helloService.sayHello(name, locale) } }

@RestController is actually just a combination of @Controller and @ResponseBody annotations.

Service

Services are beans that contain the business logic of the application.

Annotating a class with @Service makes it an injectable bean.

import org.springframework.stereotype.Service @Service class HelloService( private val helloRepository: HelloRepository ) { fun sayHello(name: String, locale: String): String { val hello = helloRepository.selectHelloInLanguage(locale) return String.format("%s %s!", hello, name) } }

Repository

The repository layer should contain the data access logic.

Annotation a class with @Repository makes it an injectable bean.

import org.springframework.jdbc.core.JdbcTemplate import org.springframework.stereotype.Repository @Repository class HelloRepository( private val jdbcTemplate: JdbcTemplate ) { fun selectHelloInLanguage(locale: String): String { return jdbcTemplate.queryForObject( "SELECT message_value FROM i18n WHERE locale = ? AND message_key = 'hello' LIMIT 1", String::class.java, arrayOf(locale), ) } }

Configuration

Configuration

Configuration classes contain the configuration logic.

Annotation a class with @Configuration makes it a configuration class that contains the configuration logic.

import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.web.client.RestTemplate @Configuration class HelloConfiguration { @Bean fun restTemplate(): RestTemplate { return RestTemplate() } @Bean fun myBean(): MyBean { // ... any custom configuration } }

Model-View-Controller

Model-View-Controller (MVC)

The Model-View-Controller (MVC) is a design pattern that separates an application into three main components:
  • Model
    represents the data and the business logic of the application
  • View
    represents the user interface
  • Controller
    handles the user input and updates the model and view

Traditional MVC Pattern

User user actions display Controller - Handles user input - Updates model Model - Business logic - Manages data View - Displays data - UI presentation updates notifies queries data Database read/write Flow: 1. User interacts with View 2. Controller handles input and updates Model 3. Model notifies View of changes 4. View queries Model and displays to User

Spring MVC

Spring Boot follows the MVC pattern, but implementation may be slightly different from the traditional MVC frameworks because of its nature as a web application framework.

For example, for RESTful applications written in Spring Boot, the controller is responsible for handling the HTTP requests and returning the HTTP responses. There is no user interface, so the view is often represented as a JSON or XML document.

Spring Boot applications implementing RESTful services usually have this architecture:

  • Controller
    handles the HTTP requests and returns the HTTP responses
  • Service
    contains the business logic
  • Repository
    contains the data access logic

Spring Boot MVC Architecture

Client (Browser, Mobile App) HTTP Request Controller @RestController Handles HTTP requests/responses request Service Layer @Service Business logic & orchestration request Repository @Repository Data access layer query Database data result response HTTP Response Request Flow Solid arrow Dashed arrow = Request down = Response up

What is API

What is API

API stands for Application Programming Interface.
  • It is a set of rules and protocols that allow different software applications to communicate with each other.
  • In contrast to user interface, API is meant for program to program or computer to computer communication.
  • There are many forms of APIs, such as web APIs, library APIs, and operating system APIs. Some APIs are specific to a particular programming language, some are specific to a particular application.

Design of API

When you think about designing an interface, you first need to think about the problem you are trying to solve with it. Consider how the API will be used, and design it in a way that is convenient for the users of the API.

This is why in this course, we will start by designing and interface (API) and only then we will implement the service and data model behind it.

If we did it the other way around, we would be tempted to design the service and data model in a way that is convenient for us, the developers, and not in a way that is convenient for the users of the API.

What is REST

What is REST

REST stands for Representational State Transfer.
It is an architectural style for designing networked applications. Systems that follow REST principles are often called RESTful systems. Characteristics of RESTful systems include statelessness, client-server architecture, and a uniform interface.
  • Statelessness
    Each request from a client to a server must contain all of the information necessary process the request, without relying on any server state being held between requests.
  • Client-server architecture
    The client and server are separate and independent of each other, only communicating by well-defined requests and responses. This allows each to be developed and scaled independently.

    In real applications, client is usually responsible for the user interactions and the server is responsible for the data storage and processing.
  • Uniform interface
    The API should be designed in a way that is consistent, predictable, handles errors gracefully, is platform-agnostic, and is easy to understand and use.

REST API

Communication through REST API is done using standard HTTP methods, such as GET, POST, PUT, and DELETE.

REST communication is request-response protocol, which means that the client sends a request to the server, and the server sends a response back to the client.

Each request is sent to a unique URI (Uniform Resource Identifier), which represents a resource on the server.

The server processes the request and sends back a response, which may include data, status, and other information, usually in JSON or XML format.

Request

  • HTTP Method
    Defines the type of action to be performed on the resource.
    GET /accounts
  • URI
    Identifies a unique resource on the server. It is usually composed of path and optionally query parameters.
    GET /accounts/123/users?limit=10&search=joe
  • Headers
    These can be used to send additional data with the request, such as the content type or an authorization token.
    Content-Type: application/json
    Authorization: Bearer some-token-value
  • Body
    Body is usually sent only with POST, PUT and PATCH requests. In most cases, this will be formatted as JSON or XML.
    { "username": "testUser", "password" : "123456" }

Response

  • HTTP Status Code
    A numerical code that indicates the success or failure of the request. There is a convention for what status code should be used in what situation.
  • Headers
    As in the request, headers in the response can be used to pass additional information. This might include the content type of the response, or a Set-Cookie header to store information in the client's browser.
  • Body
    This contains the actual data being returned from the server. This will usually be in JSON or XML format, or could also be plain text.
    { "id": 1, "username": "testUser", "email": "testUser@example.com" }

Methods

In theory, you can use all methods of HTTP protocol to communicate with REST API.

In practice, you will mostly use ...

  • GET
    Used to retrieve data from the server. It should never change the state of the server.
  • POST
    Used to send data to the server to create a new resource.
  • PUT
    Used to send data to the server to update an existing resource. Changes should be idempotent, meaning that if you send the same request multiple times, the result should be the same as if you sent it once. In other words, PUT should be used to update the resource as a whole.
  • DELETE
    Used to delete a resource from the server.
  • PATCH
    Used to partially update a resource on the server.

Paths

URI is the path to the resource on the server.

You can use path parameters to specify a particular resource, and query parameters to, for example, filter or paginate the results.

Here is the conventional structure of the resource:

/resources/{path-parameter}/sub-resource?param1=value&param2=value

  • resource path
  • path parameter
  • path and query parameter separator
  • query parameter separator
  • query parameters and their values

Headers

Headers are used to pass additional information with the request or response.

Headers are usually conventional, meaning that there are some standard headers that are used in most APIs. REST services can also define their own custom headers.

Some of the most common conventional headers are:

  • Content-Type
    Used to specify the format of the body of the request.
    Content-Type: application/json
  • Accept
    Used to specify the format of the response.
    Accept: application/json
  • Authorization
    Used to pass an authorization token with the request.
    Authorization: Bearer some-token-value
  • X-Api-Key
    Used to pass an API key with the request.
    X-Api-Key: some-api-key-value

Body

Body is usually sent only with POST, PUT and PATCH requests.

Body is almost exclusively custom, meaning that it is up to the service to define what the body of the request or response should look like.

Most common formats for the body are JSON and XML.

JSON:

{
    "id": 1234,
    "firstName": "Monika",
    "lastname": "Protivova",
    "email": "monika.protivova@gmail.com",
    "isAdmin": true
}

XML:

<user>
    <id>1234</id>
    <firstName>Monika</firstName>
    <lastname>Protivova</lastname>
    <email>monika.protivova@gmail.com</email>
    <isAdmin>true</isAdmin>
</user>

Status Codes

HTTPS status codes are used to indicate the success or failure of the request.

The most common status codes are:

  • 200 OK - Request succeeded
  • 201 Created - Request succeeded and a new resource was created
  • 400 Bad Request - The server cannot process the request due to a client error
  • 401 Unauthorized - The client must authenticate itself to get the requested response
  • 403 Forbidden - The client does not have access rights to the content
  • 404 Not Found - The server can not find the requested resource
  • 500 Internal Server Error - The server has encountered a situation it doesn't know how to handle

Status Code Cheat Sheets (for fun):

Spring Boot Controllers & Routing

Controllers in Spring Boot

Controllers are the entry point for HTTP requests in Spring Boot applications.

In Spring Boot, controllers are classes that handle incoming HTTP requests and return responses. They act as the presentation layer of your application, translating HTTP requests into business logic calls.

Controllers are responsible for:

  • Receiving and validating HTTP requests
  • Calling appropriate service layer methods
  • Transforming data between DTOs and domain models
  • Returning HTTP responses with appropriate status codes

Spring Boot provides annotations to easily define controllers and map HTTP endpoints to handler methods.

@RestController

@RestController marks a class as a REST API controller.

The @RestController annotation combines @Controller and @ResponseBody, indicating that:

  • This class will handle HTTP requests
  • All methods will return data (not views)
  • Responses will be automatically serialized to JSON
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RequestMapping @RestController @RequestMapping("/api/tasks") class TaskController { // Handler methods go here }

The @RequestMapping at the class level defines a base path for all endpoints in this controller.

@RequestMapping

@RequestMapping maps HTTP requests to handler methods or controller classes.

The @RequestMapping annotation can be used at:

  • Class level - defines base path for all endpoints in the controller
  • Method level - defines specific endpoint path and HTTP method

Examples:

@RestController @RequestMapping("/api/tasks") // Base path class TaskController { @RequestMapping(method = RequestMethod.GET) // GET /api/tasks fun getAllTasks(): List<Task> { // Return list of tasks } @RequestMapping(value = "/{id}", method = RequestMethod.GET) // GET /api/tasks/{id} fun getTask(@PathVariable id: Long): Task { // Return task by ID } }

However, @RequestMapping is verbose. Spring provides shorthand annotations for common HTTP methods.

HTTP Method Annotations

Instead of using @RequestMapping(method = ...), you can use these specialized annotations:

  • @GetMapping - Handle GET requests (retrieve data)
  • @PostMapping - Handle POST requests (create new resources)
  • @PutMapping - Handle PUT requests (update entire resource)
  • @PatchMapping - Handle PATCH requests (partial update)
  • @DeleteMapping - Handle DELETE requests (remove resources)
@RestController @RequestMapping("/api/tasks") class TaskController { @GetMapping // GET /api/tasks fun getAllTasks(): List<Task> = TODO() @GetMapping("/{id}") // GET /api/tasks/{id} fun getTaskById(@PathVariable id: Long): Task = TODO() @PostMapping // POST /api/tasks fun createTask(@RequestBody task: Task): Task = TODO() @PutMapping("/{id}") // PUT /api/tasks/{id} fun updateTask( @PathVariable id: Long, @RequestBody task: Task ): Task = TODO() @DeleteMapping("/{id}") // DELETE /api/tasks/{id} fun deleteTask(@PathVariable id: Long): Unit = TODO() }

Routing Best Practices

Follow these best practices when designing REST API routes.
  • Use nouns, not verbs
    Good: /api/tasks
    Bad: /api/getTasks
  • Use plural nouns for collections
    /api/tasks not /api/task
  • Use proper HTTP methods
    GET for retrieval, POST for creation, PUT/PATCH for updates, DELETE for removal
  • Keep URLs hierarchical
    /api/tasks/{id}/subtasks - subtasks for a specific task
  • Version your API
    /api/v1/tasks or /api/v2/tasks
  • Use consistent naming
    Stick to either camelCase or snake_case throughout your API
  • Return appropriate HTTP status codes
    200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found, etc.

Spring Boot Request Handling

Request Handling in Spring Boot

Spring Boot provides annotations to extract data from different parts of HTTP requests.

HTTP requests can contain data in multiple places:

  • Path (URL) - e.g., /api/tasks/123 where 123 is the task ID
  • Query Parameters - e.g., /api/tasks?priority=HIGH&status=TODO
  • Request Body - JSON or XML data sent with POST/PUT/PATCH requests
  • Headers - metadata like Content-Type or Authorization

Spring Boot provides annotations to easily extract this data and pass it to your handler methods.

@PathVariable

Use @PathVariable to capture dynamic parts of the URL path.

Path variables are typically used to identify specific resources, like a task ID or project ID.

@RestController @RequestMapping("/api") class TaskController( private val taskService: TaskService ) { // Single path variable @GetMapping("/tasks/{id}") fun getTaskById(@PathVariable id: Long): TaskDTO { return taskService.getTaskById(id) } // Multiple path variables @GetMapping("/tasks/{taskId}/subtasks/{subtaskId}") fun getSubtask( @PathVariable taskId: Long, @PathVariable subtaskId: Long ): SubtaskDTO { return taskService.getSubtask(taskId, subtaskId) } // Path variable with different name @GetMapping("/tasks/{id}/details") fun getTaskDetails(@PathVariable("id") taskId: Long): TaskDetailsDTO { return taskService.getTaskDetails(taskId) } // Path variable with enum @GetMapping("/tasks/priority/{priority}") fun getTasksByPriority(@PathVariable priority: TaskPriority): List<TaskDTO> { return taskService.getTasksByPriority(priority) } }

Spring Boot automatically converts path variables to the target type (Int, Long, String, Enum, etc.).

@RequestParam

Use @RequestParam to capture query parameters from the URL.

Query parameters are typically used for filtering, sorting, pagination, or passing optional data.

@RestController @RequestMapping("/api/tasks") class TaskController( private val taskService: TaskService ) { @GetMapping fun searchTasks(@RequestParam title: String): List<TaskDTO> { return taskService.searchByTitle(title) } @GetMapping fun getAllTasks( @RequestParam(defaultValue = "0") page: Int, @RequestParam(defaultValue = "10") size: Int ): List<TaskDTO> { return taskService.getAllTasks(page, size) } @GetMapping fun filterTasks( @RequestParam(required = false) priority: TaskPriority?, @RequestParam(required = false) status: TaskStatus?, @RequestParam(required = false) assignee: String?, @RequestParam(defaultValue = "false") includeArchived: Boolean ): List<TaskDTO> { return taskService.filterTasks(priority, status, assignee, includeArchived) } @GetMapping("/search") fun search(@RequestParam("q") query: String): List<TaskDTO> { return taskService.search(query) } }

@RequestBody

Use @RequestBody to receive and automatically deserialize JSON (or XML) data from the request body.

Request bodies are used with POST, PUT, and PATCH requests to send data to create or update resources.

Spring Boot automatically deserializes the JSON request body into the specified Kotlin data class using Jackson.

data class CreateTaskRequest( val title: String, val description: String?, val priority: TaskPriority, val status: TaskStatus, val dueDate: String? ) data class UpdateTaskRequest( val title: String?, val description: String?, val priority: TaskPriority?, val status: TaskStatus? ) data class AssignTaskRequest( val taskId: Long, val assigneeId: Long )
@RestController @RequestMapping("/api") class TaskController( private val taskService: TaskService ) { @PostMapping("/tasks") @ResponseStatus(HttpStatus.CREATED) fun createTask(@RequestBody request: CreateTaskRequest): TaskDTO { return taskService.createTask(request) } @PutMapping("/tasks/{id}") fun updateTask( @PathVariable id: Long, @RequestBody request: UpdateTaskRequest ): TaskDTO { return taskService.updateTask(id, request) } @PostMapping("/tasks/assign") @ResponseStatus(HttpStatus.OK) fun assignTask(@RequestBody request: AssignTaskRequest): TaskDTO { return taskService.assignTask(request) } }

Response Handling

Controllers can return different types of responses with appropriate HTTP status codes.

Spring Boot provides several ways to handle responses:

  • Direct return - Return data directly; Spring automatically serializes to JSON with 200 OK
  • @ResponseStatus - Specify custom HTTP status code
  • ResponseEntity<T> - Full control over status, headers, and body
@RestController @RequestMapping("/api/tasks") class TaskController( private val taskService: TaskService ) { @GetMapping("/{id}") fun getTask(@PathVariable id: Long): TaskDTO { return taskService.getTaskById(id) } @PostMapping @ResponseStatus(HttpStatus.CREATED) // Returns 201 Created fun createTask(@RequestBody request: CreateTaskRequest): TaskDTO { return taskService.createTask(request) } @DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) // Returns 204 No Content fun deleteTask(@PathVariable id: Long) { taskService.deleteTask(id) } }

ResponseEntity<T>

ResponseEntity<T> provides full control over the HTTP response.

Use ResponseEntity<T> when you need:

  • Dynamic status codes based on business logic
  • Custom response headers
  • Conditional responses (e.g., return different status for different scenarios)
@RestController @RequestMapping("/api/tasks") class TaskController( private val taskService: TaskService ) { @GetMapping("/{id}") fun getTask(@PathVariable id: Long): ResponseEntity<TaskDTO> { val task = taskService.findTaskById(id) return if (task != null) { ResponseEntity.ok(task) // 200 OK } else { ResponseEntity.notFound().build() // 404 Not Found } } @PostMapping fun createTask(@RequestBody request: CreateTaskRequest): ResponseEntity<TaskDTO> { val task = taskService.createTask(request) return ResponseEntity .status(HttpStatus.CREATED) // 201 Created .header("Location", "/api/tasks/${task.id}") .body(task) } @PutMapping("/{id}") fun updateTask( @PathVariable id: Long, @RequestBody request: UpdateTaskRequest ): ResponseEntity<TaskDTO> { return try { val updated = taskService.updateTask(id, request) ResponseEntity.ok(updated) // 200 OK } catch (e: TaskNotFoundException) { ResponseEntity.notFound().build() // 404 Not Found } catch (e: InvalidRequestException) { ResponseEntity.badRequest().build() // 400 Bad Request } } // No content response @DeleteMapping("/{id}") fun deleteTask(@PathVariable id: Long): ResponseEntity<Unit> { taskService.deleteTask(id) return ResponseEntity.noContent().build() // 204 No Content } }

Exercise: REST API Controller

Exercise: Coffee Shop REST API

Build a REST API for a coffee shop with full CRUD operations

Fork the Starter Project (Recommended)

1. Fork the repository:

Visit https://github.com/Monika-Protivova-Kotlin/harbour-space-coffee

Click the 'Fork' button to create your own copy

2. Clone your fork:

git clone https://github.com/YOUR_USERNAME/harbour-space-coffee.git cd harbour-space-coffee

3. Open in IntelliJ IDEA:

  • Open the project in IntelliJ IDEA
  • Wait for Gradle to download dependencies
  • Follow the instructions in the README.md
📖 All exercise details, requirements, and implementation tips are in the project's README.md

Alternative: Start from Scratch

If you prefer to start from scratch instead of forking the repository, you can create a new Spring Boot project:

Step 1: Visit Spring Initializr

Go to https://start.spring.io

Step 2: Configure Your Project

  • Project: Gradle - Kotlin
  • Language: Kotlin
  • Spring Boot: 3.x.x (latest stable version)
  • Dependencies: Spring Web, Spring Boot DevTools
  • Java: 21 or 17

Step 3: Generate and Import

  1. Click 'GENERATE' to download the project
  2. Extract the ZIP file
  3. Open the project in IntelliJ IDEA
  4. Wait for Gradle to download dependencies
⚠️ If you choose this option, you'll need to implement everything from scratch. The forked repository provides a starter structure and README with detailed requirements.

What You'll Build

A complete REST API with all HTTP methods, covering the concepts from today's lesson

Exercise Overview

You will implement a REST API for a coffee shop that demonstrates:

  • HTTP Methods - GET, POST, PUT, PATCH, DELETE
  • Path Variables - Extract IDs from URLs (/{id})
  • Query Parameters - Filter and search (?name=Latte)
  • Request Bodies - Receive JSON data with @RequestBody
  • Response Codes - 200 OK, 201 Created, 204 No Content, 404 Not Found
  • ResponseEntity - Control HTTP responses

API Endpoints You'll Create

Your coffee shop API will include:

  • GET /api/menu - List all menu items
  • GET /api/menu/{id} - Get a specific item
  • POST /api/menu - Add a new item
  • PUT /api/menu/{id} - Update an item
  • DELETE /api/menu/{id} - Remove an item
  • PATCH /api/menu/{id}/price - Update just the price
📖 Full Requirements: See the project's README.md for:
  • Detailed endpoint specifications
  • Data models and examples
  • Implementation tips and best practices
  • Testing instructions

Testing Your API

Test endpoints using:

  • Postman - Recommended (GUI for all HTTP methods)
  • IntelliJ IDEA HTTP Client - Built-in testing tool
  • Browser - For GET requests only
  • curl - Command-line testing
💡 Start simple! Get the basic GET endpoints working first, then add POST, PUT, and DELETE.