If @ResponseBody is missing: Returned strings are treated as view names (e.g., "member" → member.html). @ResponseBody tells Spring to return JSON instead, using an HttpMessageConverter.
Map to JSON: SpringBoot uses Jackson (via MappingJackson2HttpMessageConverter) to auto-convert Java objects like Map to JSON.
What if the Return Value is an Object?
Instead of a Map, you can create a DTO (Data Transfer Object) class, and it will also be automatically converted to JSON.
Important: Unlike with @ModelAttribute, a DTO that is used as a response object sent to the client must have Getter methods.
@Getter // Lombok annotation to generate getterspublic class MemberResponse { private final String email; private final String name; private final String phone; public MemberResponse(String email, String name, String phone) { this.email = email; this.name = name; this.phone = phone; }}// =======================================// Assuming MemberResponse is the DTO class you showedpublic MemberResponse postMemberJson(...) { // ... return new MemberResponse(email, name, phone);}
Tips
In real-world applications, creating a DTO class with a clear structure for responses is better for maintainability than using a generic Map.
Using @RestController on a class applies @ResponseBody to all of its methods, so you can avoid repeating the annotation.
To prevent fields with null values from being included in the final JSON output, you can use the @JsonInclude(JsonInclude.Include.NON_NULL) annotation on your DTO class.
ResponseEntity
Role
ResponseEntity is the most flexible tool in ⭐Spring MVC for constructing a custom HTTP response.
The biggest difference from simply using @ResponseBody or returning a Map is that ResponseEntity gives you full control over the response body, status code, and headers all at once.
ResponseEntity<T> responseEntity = new ResponseEntity<>(body, headers, status);
T: Response Body의 타입
body: Response Body 객체
headers: 설정할 HTTP Header
status: HTTP Response Status Code
Body
The Map or Object that will be converted to JSON.
ResponseEntity can be written more concisely using its built-in builder pattern or static helper methods.
static helper
builder pattern - 원하는 것만 끼워 넣을 수 있음
이미 구현되어 있는거 보면서 공부하면 빠르게 파악할 수 있음
3 ways to construct a ResponseEntity object
// old wayreturn new ResponseEntity<>(body, status)// A builder pattern to set a CREATED status and a body (use build/body to finish)return ResponseEntity.status(HttpStatus.CREATED).body(responseBody);return ResponseEntity.status(HttpStatus.OK).body("Here is the content.");return ResponseEntity.status(HttpStatus.NO_CONTENT).build();return ResponseEntity.noContent().build();// static helper methods// Returns a 400 Bad Request status with a specified error bodyreturn ResponseEntity.ok(responseBody); // 상태 200 OKreturn ResponseEntity.badRequest().body(errorBody);
old way
return new ResponseEntity<>(body, status)
it works (creates new object) but not preferred
builder pattern
flexible, chainable process of constructing a response (most flexible & powerful)
Use it when you need to set multiple, specific parts of the response, like a custom status and headers.
Typically starts with .status() or a helper method that returns a BodyBuilder object
use .status() to set any status code you want - especially non-shortcut ones like 201 Created or 202 Accepted
chain other methods like .body() or .headers() to build the exact response you need
have to call a terminal method like .build() or .body() to finish the process
convenient, one-line shortcuts for creating the most common types of responses for the most common HTTP statuses (200 OK, 400 Bad Request, 404 Not Found, etc.)
Terminal methods (ends a chain)
ResponseEntity.ok(yourDto);
Initiator methods (start a chain)
Helpers like badRequest(), notFound(), and accepted() return a Builder object. This allows you to chain methods like .body() or .header() onto them.
For clear communication with the client, you should set a status code that precisely matches the outcome of the request, rather than just using a generic OK (200) for every success.
// POST request -> A resource was createdreturn new ResponseEntity<>(body, HttpStatus.CREATED); // An asynchronous task was accepted for processingreturn new ResponseEntity<>(body, HttpStatus.ACCEPTED); // A response with no body contentreturn new ResponseEntity<>(body, HttpStatus.NO_CONTENT);
HttpStatus.CREATED → Responds with a 201 status.
Header
Can be added using an HttpHeaders object if necessary → when you need to implement security, caching, or other custom logic.
HttpHeaders headers = new HttpHeaders();headers.set("X-Custom-Header", "my-custom-value");return new ResponseEntity<>(responseBody, headers, HttpStatus.OK);
X-Custom-Header: An example of a user-defined header.
You can also set standard headers like Cache-Control, ETag, Location, etc.
To trigger a file download in Spring, return the file content in the HTTP response with headers indicating a “Save File” action, typically using ResponseEntity<Resource> or ResponseEntity<byte[]>.
@GetMapping("/v1/files/download")public ResponseEntity<Resource> downloadFile() throws IOException { // 서버 파일 시스템의 파일 위치 Path filePath = Paths.get("files/sample.txt"); // 파일을 InputStream 형태로 읽어서 Spring의 Resource로 래핑 Resource resource = new InputStreamResource(Files.newInputStream(filePath)); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"sample.txt\"") .contentType(MediaType.TEXT_PLAIN) .body(resource);}
Main Concepts Explained
Component
Description
InputStreamResource
An object that wraps an InputStream so that it can be sent as a response in Spring. - Java File IO
Content-Disposition: attachment
The key header that tells the browser to download the file.
MediaType.TEXT_PLAIN
Specifies the Content-Type. This can be changed to various other types like txt, pdf, jpeg, etc.