@RestController e os mapeamentos
TL;DR
@RestControllermarca a classe como handler REST: é a combinação de@Controller(registra o bean como handler HTTP) com@ResponseBody(serializa o retorno direto no corpo da resposta, sem passar por view resolver).@GetMapping,@PostMapping,@PutMapping,@DeleteMappinge@PatchMappingsão atalhos para@RequestMapping(method = …)que mapeiam métodos a pares URL + verbo HTTP. O atributopathdefine a URL;produces/consumesrestringem os media types aceitos e produzidos;params/headersadicionam condições extras sobre parâmetros de query e cabeçalhos.
O que é
@RestController é uma anotação composta introduzida no Spring 4 que reúne duas responsabilidades em uma só declaração:
| Componente | Papel |
|---|---|
@Controller | Declara a classe como um bean Spring e a registra no DispatcherServlet como handler de requisições HTTP |
@ResponseBody | Instrui o Spring a serializar o valor de retorno de cada método diretamente no corpo da resposta HTTP (por padrão, como JSON via Jackson) |
Sem @ResponseBody, o Spring MVC tentaria interpretar a String retornada como o nome de uma view (um template Thymeleaf, por exemplo), o que gera erros em APIs REST puras.
@RequestMapping é a anotação base que define os critérios de correspondência de uma requisição HTTP a um método Java. Seus atalhos por verbo são anotações compostas meta-anotadas com @RequestMapping:
| Atalho | Equivalente |
|---|---|
@GetMapping | @RequestMapping(method = RequestMethod.GET) |
@PostMapping | @RequestMapping(method = RequestMethod.POST) |
@PutMapping | @RequestMapping(method = RequestMethod.PUT) |
@DeleteMapping | @RequestMapping(method = RequestMethod.DELETE) |
@PatchMapping | @RequestMapping(method = RequestMethod.PATCH) |
Por que importa
APIs REST são o contrato público de praticamente todo serviço backend moderno. @RestController e os mapeamentos são o ponto de entrada de cada operação: errar o verbo, a URL ou o media type significa errar a API antes mesmo de chegar na lógica de negócio.
Do ponto de vista de entrevista sênior, as perguntas mais frequentes nessa área são:
- “Qual a diferença entre
@Controllere@RestController?” — A presença implícita de@ResponseBody. - “Por que usar
@GetMappingem vez de@RequestMapping(method = GET)?” — É mais legível, expressivo e menos verboso; é o estilo recomendado desde Spring 4.3. - “
produceseconsumesafetam roteamento ou só serialização?” — Afetam o roteamento: uma requisição sem oContent-Typecorreto recebe415 Unsupported Media Typeantes de chegar ao método.
Como funciona
@Controller vs @RestController (view vs body)
Em Spring MVC o fluxo padrão de um @Controller é:
- O método do controller retorna uma
String(nome da view) ou umModelAndView. - O
ViewResolverlocaliza o template correspondente. - O template é renderizado e enviado como HTML.
Com @ResponseBody (ou @RestController), o fluxo muda:
- O método retorna um objeto Java (POJO,
ResponseEntity,List, etc.). - Um
HttpMessageConverter(geralmente Jackson) serializa o objeto para JSON/XML. - O payload serializado é escrito diretamente no corpo da resposta.
Não existe view resolver envolvido. O Content-Type da resposta é determinado pelo Accept do cliente e pelo atributo produces do mapeamento.
@RequestMapping e os atalhos por verbo HTTP
@RequestMapping pode ser aplicado no nível da classe (prefixo de rota comum) e no nível do método (rota e verbo específicos):
@RestController
@RequestMapping("/orders") // prefixo comum para todos os métodos
public class OrderController {
@GetMapping // GET /orders
public List<Order> listAll() { … }
@GetMapping("/{id}") // GET /orders/{id}
public Order findById(@PathVariable Long id) { … }
@PostMapping // POST /orders
@ResponseStatus(HttpStatus.CREATED)
public Order create(@RequestBody Order order) { … }
@PutMapping("/{id}") // PUT /orders/{id}
public Order replace(@PathVariable Long id, @RequestBody Order order) { … }
@PatchMapping("/{id}/status") // PATCH /orders/{id}/status
public Order updateStatus(@PathVariable Long id,
@RequestBody StatusUpdate update) { … }
@DeleteMapping("/{id}") // DELETE /orders/{id}
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) { … }
}A URL final de cada método é a concatenação do path da classe com o path do método.
Atributos: path, method, produces/consumes, params/headers
path (alias: value) — padrão de URL. Suporta variáveis de template ({id}), wildcards (*, **) e expressões regex ({version:\\d+\\.\\d+}):
@GetMapping("/products/{id}") // variável simples
@GetMapping("/files/**") // qualquer subpath
@GetMapping("/v{version:\\d+}/orders") // versão numérica na URLmethod — verbo(s) HTTP aceitos. Os atalhos fazem isso implicitamente; use @RequestMapping(method = {RequestMethod.GET, RequestMethod.HEAD}) quando precisar de múltiplos verbos no mesmo mapeamento.
produces — restringe o mapeamento ao Accept do cliente. A requisição só é roteada para o método se o cliente aceitar um dos media types declarados:
@GetMapping(path = "/orders/{id}", produces = "application/json")
public Order findById(@PathVariable Long id) { … }
// Ou usando constante:
@GetMapping(path = "/orders/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public Order findById(@PathVariable Long id) { … }Se nenhum método for compatível com o Accept, o Spring retorna 406 Not Acceptable.
consumes — restringe o mapeamento ao Content-Type do corpo enviado pelo cliente. Essencial para POST/PUT/PATCH:
@PostMapping(path = "/orders", consumes = MediaType.APPLICATION_JSON_VALUE)
public Order create(@RequestBody Order order) { … }Se o Content-Type não bater, o Spring retorna 415 Unsupported Media Type.
params — filtra por parâmetros de query. Raramente usado em REST puro, mas útil para versionar por query param ou para feature flags:
@GetMapping(path = "/orders", params = "status=PENDING")
public List<Order> listPending() { … }
@GetMapping(path = "/orders", params = "!status") // sem param "status"
public List<Order> listAll() { … }headers — filtra por cabeçalhos HTTP arbitrários (mesma sintaxe de params). Para Content-Type e Accept, prefira consumes/produces; use headers para cabeçalhos customizados:
@GetMapping(path = "/orders", headers = "X-API-Version=2")
public List<OrderV2> listAllV2() { … }Precedência de
produces/consumesno nível do métodoQuando declarados no método, substituem (não estendem) os valores da classe. Se a classe declara
produces = "application/json"e o método declaraproduces = "application/xml", o método aceita apenas XML.
Na prática
Exemplo completo de um controller de pedidos com os principais mapeamentos:
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping(
path = "/orders",
produces = MediaType.APPLICATION_JSON_VALUE
)
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
// GET /orders — lista todos os pedidos
@GetMapping
public List<Order> listAll() {
return orderService.findAll();
}
// GET /orders/{id} — busca por ID
@GetMapping("/{id}")
public ResponseEntity<Order> findById(@PathVariable Long id) {
return orderService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// POST /orders — cria novo pedido
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.CREATED)
public Order create(@Valid @RequestBody Order order) {
return orderService.save(order);
}
// PUT /orders/{id} — substitui pedido inteiro
@PutMapping(path = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE)
public Order replace(@PathVariable Long id,
@Valid @RequestBody Order order) {
return orderService.replace(id, order);
}
// PATCH /orders/{id}/status — atualiza status parcialmente
@PatchMapping(path = "/{id}/status", consumes = MediaType.APPLICATION_JSON_VALUE)
public Order updateStatus(@PathVariable Long id,
@RequestBody StatusUpdate update) {
return orderService.updateStatus(id, update.status());
}
// DELETE /orders/{id} — remove pedido
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
orderService.delete(id);
}
}Pontos destacados:
producesdeclarado na classe aplica a todos os métodos (todos respondem JSON).consumesdeclarado em cada método de escrita garante que apenas JSON é aceito no corpo.ResponseEntitypermite controlar o status HTTP dinamicamente (ex.:404quando não encontrado).- Construtor injection (sem
@Autowiredno campo) é o estilo recomendado para beans Spring.
Armadilhas
(1) Usar @Controller sem @ResponseBody em uma API REST
Problema: Ao usar @Controller puro em um endpoint REST, o Spring MVC interpreta o valor de retorno como um nome de view. Se não existir um ViewResolver configurado para aquele nome, a aplicação lança jakarta.servlet.ServletException: Could not resolve view with name '...' ou retorna 500.
Exemplo que falha:
@Controller
@RequestMapping("/products")
public class ProductController {
@GetMapping("/{id}")
public Product findById(@PathVariable Long id) { // Spring tenta resolver "Product@7f3d..." como view
return productService.findById(id);
}
}Fix: Trocar @Controller por @RestController, ou adicionar @ResponseBody no método:
@Controller
@RequestMapping("/products")
public class ProductController {
@GetMapping("/{id}")
@ResponseBody // serializa o retorno como JSON
public Product findById(@PathVariable Long id) {
return productService.findById(id);
}
}(2) Dois métodos com mapeamento ambíguo
Problema: Definir dois métodos com o mesmo verbo e o mesmo path gera IllegalStateException na inicialização do contexto Spring: Ambiguous mapping. Cannot map '…' method.
Exemplo que falha:
@GetMapping("/orders")
public List<Order> listAll() { … }
@GetMapping("/orders") // mesmo verbo + mesmo path → ambiguidade
public List<Order> listActive() { … }Fix: Diferenciar os mapeamentos por path, por params ou por headers:
@GetMapping("/orders")
public List<Order> listAll() { … }
@GetMapping(path = "/orders", params = "active=true")
public List<Order> listActive() { … }Ou, mais idiomático em REST: usar path diferente (/orders vs /orders?active=true tratado via @RequestParam num único método).
(3) @PostMapping sem consumes aceitando qualquer Content-Type
Problema: Sem consumes, o Spring aceita qualquer Content-Type no corpo. Se o cliente enviar text/plain ou application/x-www-form-urlencoded por engano, o Jackson pode falhar ao desserializar com HttpMessageNotReadableException, retornando 400 Bad Request com mensagem de erro pouco descritiva. Pior: ambiguidades de mapeamento entre form-encoded e JSON ficam silenciosas.
Exemplo sem restrição:
@PostMapping("/orders")
public Order create(@RequestBody Order order) { … } // aceita qualquer Content-TypeFix: Declarar explicitamente consumes:
@PostMapping(path = "/orders", consumes = MediaType.APPLICATION_JSON_VALUE)
public Order create(@RequestBody Order order) { … }Agora requisições com Content-Type errado recebem 415 Unsupported Media Type antes de chegar ao método — resposta clara, sem stack trace no log.
Em entrevista
Frase pronta (inglês)
“@RestController is a composed annotation that combines @Controller and @ResponseBody, so the return value of each handler method is serialized directly into the HTTP response body — typically as JSON — rather than being resolved as a view name. For routing, @GetMapping, @PostMapping, and the other shortcuts are composed annotations built on top of @RequestMapping that bind a method to a specific HTTP verb and path. The produces and consumes attributes go further: they narrow the mapping based on the Accept and Content-Type headers, so if a client sends the wrong content type, Spring rejects the request with 415 Unsupported Media Type before the method body even runs.”
Vocabulário
| Termo PT | Termo EN |
|---|---|
| Mapeamento de requisição | Request mapping |
| Anotação composta | Composed annotation |
| Corpo da resposta | Response body |
| Resolvedor de view | View resolver |
| Tipo de media (conteúdo) | Media type / Content type |
| Verbo HTTP | HTTP method / HTTP verb |
| Atalho de anotação | Annotation shortcut / shorthand |
| Serialização | Serialization |
| Desserialização | Deserialization |
| Negociação de conteúdo | Content negotiation |
Veja também
- O que é Spring MVC
- Recebendo dados da request
- JAX-RS (resource methods da spec — o outro caminho)
- Beans e estereótipos (o controller é um estereótipo/bean)
- Web e APIs REST
- Trilha Java
- Verbetes: @RestController, @RequestMapping, @PostMapping
Referências
- Spring Framework Reference — Handler Methods: Request Mapping: https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-requestmapping.html