Spring Boot
REST service
HTTP headers
Java
web development

How to set respond header values in Spring Boot rest service method?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

In Spring Boot, response headers are usually set for caching, content disposition, custom metadata, or location information after resource creation. The cleanest implementation depends on where the header value comes from: controller logic, framework infrastructure, or low-level servlet code.

Prefer ResponseEntity in Controller Methods

For most REST endpoints, ResponseEntity is the best choice because it keeps the status code, headers, and body in one return value. That makes the method easy to test and avoids mixing transport details with servlet APIs.

java
1import org.springframework.http.HttpHeaders;
2import org.springframework.http.MediaType;
3import org.springframework.http.ResponseEntity;
4import org.springframework.web.bind.annotation.GetMapping;
5import org.springframework.web.bind.annotation.RestController;
6
7@RestController
8class ReportController {
9
10    @GetMapping("/report")
11    ResponseEntity<String> getReport() {
12        HttpHeaders headers = new HttpHeaders();
13        headers.setContentType(MediaType.TEXT_PLAIN);
14        headers.setCacheControl("no-store");
15        headers.set("X-Report-Version", "2026-03");
16
17        return ResponseEntity
18            .ok()
19            .headers(headers)
20            .body("report-ready");
21    }
22}

This pattern scales well when you need to add conditional headers such as ETag, Location, or a download filename. The method remains declarative instead of mutating shared response state.

Use HttpServletResponse for Direct Streaming

If you are writing directly to the output stream, the servlet response is still valid. It is especially common for file downloads where the body is produced incrementally.

java
1import jakarta.servlet.http.HttpServletResponse;
2import org.springframework.web.bind.annotation.GetMapping;
3import org.springframework.web.bind.annotation.RestController;
4
5import java.io.IOException;
6import java.nio.charset.StandardCharsets;
7
8@RestController
9class DownloadController {
10
11    @GetMapping("/download")
12    void download(HttpServletResponse response) throws IOException {
13        response.setContentType("text/plain");
14        response.setHeader("Content-Disposition", "attachment; filename=report.txt");
15        response.setHeader("Cache-Control", "no-store");
16
17        byte[] data = "downloaded content\n".getBytes(StandardCharsets.UTF_8);
18        response.getOutputStream().write(data);
19        response.flushBuffer();
20    }
21}

This is more imperative, so use it when you really need stream control. For ordinary JSON responses, ResponseEntity is usually easier to maintain.

Returning Headers for Resource Creation

REST APIs often need to return a Location header after creating a resource. Spring makes this straightforward with ResponseEntity.created.

java
1import org.springframework.http.ResponseEntity;
2import org.springframework.web.bind.annotation.PostMapping;
3import org.springframework.web.bind.annotation.RequestBody;
4import org.springframework.web.bind.annotation.RestController;
5
6import java.net.URI;
7import java.util.Map;
8
9@RestController
10class UserController {
11
12    @PostMapping("/users")
13    ResponseEntity<Map<String, Object>> createUser(@RequestBody Map<String, String> payload) {
14        long userId = 42L;
15        URI location = URI.create("/users/" + userId);
16
17        return ResponseEntity
18            .created(location)
19            .header("X-Created-By", "signup-service")
20            .body(Map.of("id", userId, "name", payload.get("name")));
21    }
22}

That produces a conventional API response without forcing you to manipulate headers separately from the status code.

Keep Cross-Cutting Headers Out of Individual Methods

Some headers do not belong inside every controller. Security headers, CORS policy, and many cache rules are better handled with filters, interceptors, or Spring Security configuration. The controller method should usually set only headers that are specific to that endpoint's business behavior.

That separation matters because duplicated header logic becomes inconsistent quickly. If five endpoints all set Cache-Control manually, one of them will eventually drift. Infrastructure-level headers should live in infrastructure code.

Testing also gets easier when the header logic stays explicit. A MockMvc test can assert Location, Cache-Control, or custom metadata directly from the controller response without having to inspect servlet internals.

Common Pitfalls

  • Using HttpServletResponse everywhere even when ResponseEntity would be simpler and easier to test.
  • Setting the same cross-cutting headers in many controller methods instead of a filter or centralized configuration.
  • Forgetting that some headers, such as Content-Disposition, require careful quoting and formatting.
  • Writing to the response output stream and also trying to return a normal body from the same method.
  • Adding custom headers but not checking whether downstream proxies or browsers actually expose or preserve them.

Summary

  • 'ResponseEntity is the usual Spring Boot answer for setting headers in a REST method.'
  • 'HttpServletResponse is appropriate when you need low-level streaming control.'
  • Resource-creation endpoints commonly use Location through ResponseEntity.created.
  • Endpoint-specific headers belong in the controller; cross-cutting headers belong in shared infrastructure.
  • Choose the highest-level API that still matches the response you need to build.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.