Lesson 4
Mastering HTTP Headers with Spring Boot Kotlin
Introduction

Welcome back to your advanced journey in mastering RESTful techniques with Spring Boot! In our previous lessons, we delved into validating request data, handling exceptions gracefully, and negotiating content types. Today, we’re stepping into the realm of HTTP headers — key-value pairs that play a crucial role in RESTful services. You will learn what HTTP headers are, their use cases, and how to work with them effectively in Spring Boot.

What Are HTTP Headers?

You're already familiar with key components of HTTP requests and responses, such as HTTP methods, URIs, and bodies. Now it’s time to delve into HTTP headers. Simply put, headers are metadata associated with requests and responses, formatted as key-value pairs. They provide essential information that helps both the client and server understand how to process the data being exchanged.

Here's an example of an HTTP request showing the URI, method, body, and headers:

HTTP
1POST /api/todos HTTP/1.1 2Host: example.com 3Content-Type: application/json 4Authorization: Bearer <token> 5Accept: application/json 6Accept-Language: en-US 7 8{ 9 "title": "Finish homework", 10 "description": "Complete the math and science homework.", 11 "dueDate": "2023-10-20" 12}

In this example:

  • Method: POST
  • URI: /api/todos
  • Headers: Content-Type, Authorization, Accept, and Accept-Language
  • Body: The JSON payload contains the to-do item details
Common Use Cases for HTTP Headers

HTTP headers serve many purposes. Here are some common headers and how they're used:

  • Accept: Informs the server about the types of data the client can process (Accept: application/json).
  • Accept-Language: Specifies the preferred languages (Accept-Language: en-US, fr-CA).
  • Authorization: Contains credentials for authenticating the client (Authorization: Bearer <token>).
  • Cache-Control: Directs caching mechanisms (Cache-Control: no-cache).
  • Content-Type: Indicates the media type of the resource (Content-Type: application/json).
  • Content-Length: The size of the resource in bytes (Content-Length: 348).

Sometimes headers can have multiple values; for example:

  • Set-Cookie: Allows the server to pass multiple cookies (Set-Cookie: sessionId=abc; Expires=Wed, 09 Jun 2021 10:18:14 GMT;).
Retrieving a Specific Header from the Request

Let's start by retrieving a specific header from the request by its name. Here’s an example using the @RequestHeader annotation:

Kotlin
1@GetMapping("/todos/{id}") 2fun getTodoItemById(@PathVariable id: Int, @RequestHeader("Accept-Language") acceptLanguage: String): ResponseEntity<TodoItem> { 3 println("Accept-Language: $acceptLanguage") 4 val todoItem = todoItemRepository.findById(id) 5 .orElseThrow { NoSuchElementException("Todo item with id $id not found") } 6 return ResponseEntity(todoItem, HttpStatus.OK) 7}

In this snippet, the function getTodoItemById retrieves a specific to-do item by its ID. The @RequestHeader("Accept-Language") annotation extracts the Accept-Language header from the HTTP request and assigns it to the acceptLanguage variable. This value is then printed to the console.

Using Optional HTTP Headers with Kotlin Null Safety

Instead of using java.util.Optional, Kotlin's null safety features can handle the absence of a header gracefully:

Kotlin
1@GetMapping("/todos/{id}") 2fun getTodoItemById(@PathVariable id: Int, @RequestHeader("Accept-Language") acceptLanguage: String?): ResponseEntity<TodoItem> { 3 acceptLanguage?.let { println("Accept-Language: $it") } 4 val todoItem = todoItemRepository.findById(id) 5 .orElseThrow { NoSuchElementException("Todo item with id $id not found") } 6 return ResponseEntity(todoItem, HttpStatus.OK) 7}

In this example, the @RequestHeader("Accept-Language") annotation uses a nullable String to handle the optional Accept-Language header. The let function prints the header value if it is present.

Retrieving All Headers from the Request

To retrieve all headers from a request, we can use a Map or a MultiValueMap:

Kotlin
1@GetMapping("/todos") 2fun getAllTodoItems(@RequestHeader headers: Map<String, String>): ResponseEntity<List<TodoItem>> { 3 headers.forEach { (key, value) -> println("$key: $value") } 4 return ResponseEntity(todoItemRepository.findAll(), HttpStatus.OK) 5}

Here, the getAllTodoItems function retrieves all to-do items. The @RequestHeader Map<String, String> headers annotation captures all headers from the request and stores them in a Map. The forEach loop prints each header key and its corresponding value to the console.

Or using MultiValueMap:

Kotlin
1@GetMapping("/todos") 2fun getAllTodoItems(@RequestHeader headers: MultiValueMap<String, String>): ResponseEntity<List<TodoItem>> { 3 headers.forEach { (key, value) -> println("$key: ${value.joinToString(", ")}") } 4 return ResponseEntity(todoItemRepository.findAll(), HttpStatus.OK) 5}

This function is similar to the previous one, but it uses a MultiValueMap to capture headers, allowing the handling of headers with multiple values. The forEach loop prints each header and joins multiple values using a comma.

Leveraging the HttpHeaders Class

We can also use the HttpHeaders class for the previous functionalities. Here’s how:

Kotlin
1@GetMapping("/todos/{id}") 2fun getTodoItemById(@PathVariable id: Int, @RequestHeader headers: HttpHeaders): ResponseEntity<TodoItem> { 3 val acceptLanguage = headers.getFirst("Accept-Language") 4 println("Accept-Language: $acceptLanguage") 5 val todoItem = todoItemRepository.findById(id) 6 .orElseThrow { NoSuchElementException("Todo item with id $id not found") } 7 return ResponseEntity(todoItem, HttpStatus.OK) 8}

In this example, the @RequestHeader HttpHeaders headers annotation captures all request headers into an HttpHeaders object. The function retrieves the first Accept-Language header using headers.getFirst("Accept-Language") and prints it.

Kotlin
1@GetMapping("/todos") 2fun getAllTodoItems(@RequestHeader headers: HttpHeaders): ResponseEntity<List<TodoItem>> { 3 headers.forEach { (key, value) -> println("$key: ${value.joinToString(", ")}") } 4 return ResponseEntity(todoItemRepository.findAll(), HttpStatus.OK) 5}

Similar to the previous snippet, this function captures all request headers using HttpHeaders. The forEach loop prints each header key and its corresponding values joined by commas.

Adding Headers to the Response

To add headers to the response, we can use the ResponseEntity class, which allows us to set headers easily:

Kotlin
1@GetMapping("/todos") 2fun getAllTodoItems(): ResponseEntity<List<TodoItem>> { 3 val responseHeaders = HttpHeaders() 4 responseHeaders["X-Custom-Header"] = "CustomHeaderValue" 5 return ResponseEntity(todoItemRepository.findAll(), responseHeaders, HttpStatus.OK) 6}

In this example, the getAllTodoItems function adds a custom header to the response. A new HttpHeaders object is created, and a custom header (X-Custom-Header) is set. The ResponseEntity constructor includes the list of to-do items, response headers, and the HTTP status code.

Summary

In this lesson, we explored the significance of HTTP headers in RESTful services, learned how to retrieve specific and all headers from requests, utilized the HttpHeaders class for enhanced readability, and added custom headers to responses. Equipped with this knowledge, you are ready to tackle the forthcoming practice exercises where you'll apply these concepts hands-on.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.