Como tratar erro 400 JSON Spring Boot sem resposta genérica

Você recebe um chamado dizendo que a API está devolvendo 400, mas o cliente só enxerga algo genérico como Bad Request e não sabe o que corrigir. Por outro lado, quando o corpo da requisição chega quebrado, com vírgula sobrando, aspas faltando ou tipo incompatível, o Spring Boot costuma falhar antes mesmo de executar o método do controller. Ao mesmo tempo, se a dúvida é como tratar erro 400 json spring boot, a resposta prática é esta: entender que o erro nasce na desserialização do @RequestBody, capturar a exceção correta e devolver um payload de erro claro, previsível e padronizado. Depois de ajustar esse trecho, o proximo passo natural e seguir para aprofundar em como tratar exceções no spring boot com @controlleradvice e @exceptionhandler.

Esse é um ponto que aparece muito em API real porque erro de JSON inválido não é raro. Por outro lado, mobile antigo, integração de terceiro, cliente front sem contrato bem fechado, refactor que mudou nome de campo sem alinhar consumidor. Ao mesmo tempo, o efeito é o mesmo: a requisição nem chega direito na camada de negócio, e a resposta padrão do framework raramente é a melhor experiência para quem consome a API. Se fizer sentido comparar com outra abordagem do ecossistema Spring, veja comparar com Guia completo de testes no Spring Boot: evite falhas em produção.

Também existe uma decisão arquitetural importante aqui. Por outro lado, dá para tratar isso localmente em um controller específico, mas em APIs com vários endpoints o custo de manutenção sobe rápido. Ao mesmo tempo, em geral, a comparação relevante não é só técnica; é de consistência operacional. Na prática, resposta padrão e centralizada quase sempre ganha de solução espalhada. Se fizer sentido comparar com outra abordagem do ecossistema Spring, veja comparar com JWT no Spring Security com Spring Boot: autenticação moderna passo a passo.

Como tratar erro 400 json spring boot: secao pratica com codigo completo

Na pratica, um exemplo enxuto ajuda a sair da teoria e evitar erro comum de producao quando o projeto cresce. Para aprofundar essa decisao sem criar outra URL concorrente, o melhor complemento aqui e comparar com Guia completo de Spring Data JPA no Spring Boot sem dor.

@RestController
@RequestMapping("/api/exemplo")
public class ExemploController {
  @GetMapping
  public ResponseEntity<String> listar() {
    return ResponseEntity.ok("ok");
  }
}

Por que o Spring Boot lança erro quando recebe JSON inválido

Quando um endpoint recebe algo como @RequestBody ClienteRequest request, o Spring usa um conversor HTTP para transformar o JSON em objeto Java. Por outro lado, na prática, quem costuma fazer esse trabalho é o Jackson. Ao mesmo tempo, se o JSON está malformado ou incompatível com a estrutura esperada, a conversão falha na etapa anterior à execução do método. Depois de ajustar esse trecho, o proximo passo natural e seguir para aprofundar em responseentity no spring boot: quando usar, exemplos e erros comuns.

É aqui que entra a exceção mais comum desse cenário: HttpMessageNotReadableException. Por outro lado, ela costuma ser um invólucro para erros mais específicos do Jackson, como JSON com sintaxe inválida, enum fora do esperado, tipo numérico em campo string ou data em formato incompatível. Ao mesmo tempo, em busca real, muita gente procura por httpmessagenotreadableexception spring boot porque é exatamente o nome que aparece no log.

O detalhe que pega muita gente é este: se o parse do corpo falhar, o controller nem é chamado. Por outro lado, então não adianta tentar resolver isso dentro da regra de negócio, porque a falha acontece antes. Ao mesmo tempo, essa diferença explica por que um spring boot requestbody json invalido costuma exigir tratamento via mecanismo de exceções do framework, e não só um if dentro do método.

Considere este endpoint:

@PostMapping("/clientes")
public ResponseEntity<ClienteResponse> criar(@RequestBody ClienteRequest request) {
    ClienteResponse response = clienteService.criar(request);
    return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

Se o cliente enviar:

{
  "nome": "Ana",
  "idade": 30,

o JSON está truncado. Por outro lado, o Jackson tenta desserializar, falha, o Spring encapsula essa falha e devolve 400. Ao mesmo tempo, o método criar nem entra em execução.

Como tratar erro 400 JSON Spring Boot de forma útil para quem consome a API

O problema não é o 400 em si. Por outro lado, o status está correto. Ao mesmo tempo, o problema é devolver uma resposta pobre, instável ou técnica demais. Na prática, em produção, o consumidor precisa entender se o corpo está malformado, se um campo veio com tipo errado ou se faltou algum atributo obrigatório do ponto de vista sintático. Ainda assim, isso reduz retrabalho e baixa ruído no suporte.

Uma boa resposta para JSON inválido costuma ter alguns elementos simples: timestamp, status, erro, mensagem amigável, caminho da requisição e, quando fizer sentido, um detalhe mais específico. Por outro lado, não é preciso despejar o stack trace nem a mensagem crua do Jackson para o cliente.

Um modelo simples de payload:

public record ApiError(
    Instant timestamp,
    int status,
    String error,
    String message,
    String path
) {}

Agora o handler global:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<ApiError> handleHttpMessageNotReadable(
            HttpMessageNotReadableException ex,
            HttpServletRequest request) {

        String message = "JSON inválido no corpo da requisição.";

        Throwable cause = ex.getCause();
        if (cause instanceof com.fasterxml.jackson.core.JsonParseException) {
            message = "JSON malformado. Verifique vírgulas, aspas e chaves.";
        } else if (cause instanceof com.fasterxml.jackson.databind.exc.InvalidFormatException) {
            message = "Um ou mais campos foram enviados em formato inválido.";
        } else if (cause instanceof com.fasterxml.jackson.databind.exc.MismatchedInputException) {
            message = "A estrutura do JSON não corresponde ao formato esperado pela API.";
        }

        ApiError body = new ApiError(
                Instant.now(),
                HttpStatus.BAD_REQUEST.value(),
                "Bad Request",
                message,
                request.getRequestURI()
        );

        return ResponseEntity.badRequest().body(body);
    }
}

Resposta esperada:

{
  "timestamp": "2026-04-24T10:15:30Z",
  "status": 400,
  "error": "Bad Request",
  "message": "JSON malformado. Verifique vírgulas, aspas e chaves.",
  "path": "/clientes"
}

Isso já resolve grande parte do problema sem complicar a aplicação. Por outro lado, se quiser aprofundar em como tratar exceções no spring boot com @controlleradvice e @exceptionhandler, esse assunto conversa diretamente com a decisão que aparece aqui.

Spring Boot requestbody JSON inválido: tratamento local vs handler global

A comparação que interessa em API real é esta: tratar no próprio controller ou centralizar com @RestControllerAdvice?

Abordagem local no controller

Em tese, você pode criar um método anotado com @ExceptionHandler dentro de um controller específico. Por outro lado, isso funciona bem quando um endpoint isolado precisa de resposta diferente por contrato, talvez porque integra com um parceiro externo que exige payload de erro customizado só naquele recurso.

@RestController
@RequestMapping("/clientes")
public class ClienteController {

    @PostMapping
    public ResponseEntity<ClienteResponse> criar(@RequestBody ClienteRequest request) {
        ClienteResponse response = clienteService.criar(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(response);
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<ApiError> handleJsonInvalido(
            HttpMessageNotReadableException ex,
            HttpServletRequest request) {

        ApiError body = new ApiError(
                Instant.now(),
                400,
                "Bad Request",
                "JSON inválido no corpo da requisição.",
                request.getRequestURI()
        );

        return ResponseEntity.badRequest().body(body);
    }
}

O lado ruim aparece rápido. Por outro lado, se amanhã você tiver dez controllers, vai repetir lógica ou criar variações sem perceber. Ao mesmo tempo, um endpoint responde com message, outro com detalhe, outro expõe erro interno. Na prática, a consistência da API começa a degradar.

Abordagem global com @RestControllerAdvice

Na maioria dos projetos, essa é a melhor escolha. Por outro lado, você concentra o tratamento em um único lugar, padroniza resposta e facilita manutenção. Ao mesmo tempo, quando novos endpoints surgem, o comportamento já vem junto. Na prática, em time maior, isso evita que cada dev invente um formato de erro diferente.

A comparação explícita aqui é simples: a abordagem pior para APIs médias e grandes é espalhar tratamento de parse em vários controllers; a abordagem melhor é centralizar o erro técnico transversal em um handler global. Por outro lado, o ganho não é só elegância. Ao mesmo tempo, é previsibilidade para cliente, log mais claro e menos retrabalho.

Se você quiser aprofundar em responseentity no spring boot: quando usar, exemplos e erros comuns, faz sentido porque a resposta de erro também é parte do contrato HTTP e merece o mesmo cuidado da resposta de sucesso.

Exemplo prático completo para JSON inválido no Spring Boot com erro 400

Segue uma implementação enxuta, mas pronta para uso, separando DTO, controller e handler global.

DTO da requisição

public class ClienteRequest {

    private String nome;
    private Integer idade;
    private String email;

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public Integer getIdade() {
        return idade;
    }

    public void setIdade(Integer idade) {
        this.idade = idade;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

Controller

@RestController
@RequestMapping("/clientes")
public class ClienteController {

    @PostMapping
    public ResponseEntity<Map<String, Object>> criar(@RequestBody ClienteRequest request) {
        Map<String, Object> body = new HashMap<>();
        body.put("mensagem", "Cliente criado com sucesso");
        body.put("nome", request.getNome());
        return ResponseEntity.status(HttpStatus.CREATED).body(body);
    }
}

Payload de erro

public record ApiError(
    Instant timestamp,
    int status,
    String error,
    String message,
    String path
) {}

Handler global

@RestControllerAdvice
public class ApiExceptionHandler {

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<ApiError> handleJsonInvalido(
            HttpMessageNotReadableException ex,
            HttpServletRequest request) {

        String message = resolverMensagem(ex);

        ApiError error = new ApiError(
                Instant.now(),
                HttpStatus.BAD_REQUEST.value(),
                "Bad Request",
                message,
                request.getRequestURI()
        );

        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }

    private String resolverMensagem(HttpMessageNotReadableException ex) {
        Throwable cause = ex.getCause();

        if (cause instanceof com.fasterxml.jackson.core.JsonParseException) {
            return "JSON malformado. Revise a sintaxe do corpo enviado.";
        }

        if (cause instanceof com.fasterxml.jackson.databind.exc.InvalidFormatException invalidFormatException) {
            if (!invalidFormatException.getPath().isEmpty()) {
                String campo = invalidFormatException.getPath().get(0).getFieldName();
                return "O campo '" + campo + "' foi enviado com formato inválido.";
            }
            return "Um campo foi enviado com formato inválido.";
        }

        if (cause instanceof com.fasterxml.jackson.databind.exc.MismatchedInputException mismatchedInputException) {
            if (!mismatchedInputException.getPath().isEmpty()) {
                String campo = mismatchedInputException.getPath().get(0).getFieldName();
                return "O campo '" + campo + "' não corresponde ao formato esperado.";
            }
            return "A estrutura do JSON não corresponde ao esperado.";
        }

        return "JSON inválido no corpo da requisição.";
    }
}

Agora alguns cenários reais.

Situação 1 de produção: um app mobile antigo continua enviando idade como texto, por exemplo “idade”: “trinta”. Por outro lado, o endpoint espera Integer. Ao mesmo tempo, sem tratamento dedicado, o consumidor recebe um 400 genérico. Na prática, com o handler acima, a resposta pode indicar que o campo idade veio em formato inválido. Ainda assim, isso reduz idas e vindas entre backend e mobile.

Situação 2 de produção: uma integração B2B envia payload assinado, mas um gateway intermediário altera encoding ou corta parte do corpo. Por outro lado, o resultado vira JSON truncado. Ao mesmo tempo, o seu suporte enxerga aumento de 400, mas se a resposta for minimamente clara e o log interno guardar a causa, o diagnóstico fica muito mais rápido.

Para reforçar confiabilidade, vale comparar com Guia completo de testes no Spring Boot: evite falhas em produção. Por outro lado, tratar exceção sem testar payload quebrado é uma aposta ruim.

Erros comuns ao tratar JSON inválido no Spring Boot

Tem algumas armadilhas recorrentes nesse tema, e a maioria aparece quando o projeto cresce.

Erro comum: retornar a mensagem crua da exceção

Causa: usar ex.getMessage() diretamente na resposta.

Sintoma: a API passa a devolver mensagens enormes, técnicas e difíceis de versionar, além de variar conforme atualização do Jackson.

Correção: mapear os tipos principais para mensagens estáveis e humanas. Por outro lado, o detalhe completo fica no log.

Erro comum: tratar JSON inválido como erro de validação

Causa: assumir que tudo relacionado a entrada ruim é Bean Validation.

Sintoma: o time cria mensagens de campo obrigatório para um payload que nem chegou a virar objeto Java.

Correção: separar claramente parse de JSON, validação de DTO e regra de negócio. Por outro lado, isso evita respostas enganosas.

Erro comum: resolver em um único controller e esquecer o resto

Causa: começar com um endpoint e replicar solução local aos poucos.

Sintoma: cada recurso devolve erro num formato diferente, o front precisa fazer tratamento especial por rota e o contrato da API fica inconsistente.

Correção: centralizar em @RestControllerAdvice quando o objetivo é comportamento padronizado na aplicação inteira.

Quando usar e quando evitar cada abordagem

Se a intenção é decidir entre tratamento local e global, a regra prática é bem objetiva.

Use handler global quando o erro é transversal, como parse de JSON, validação padronizada, recurso não encontrado e falhas comuns de contrato HTTP. Por outro lado, esse costuma ser o cenário dominante em APIs internas e públicas. Ao mesmo tempo, a manutenção melhora, o onboarding do time fica mais simples e o cliente recebe comportamento previsível.

Use tratamento local quando um controller ou um pequeno conjunto de endpoints precisa deliberadamente de um contrato diferente. Por outro lado, isso pode acontecer em integração legada, endpoint administrativo ou rota com exigência externa específica. Mesmo assim, eu usaria com parcimônia. Na prática, solução local demais costuma envelhecer mal.

Evite espalhar lógica de mensagem em vários pontos e, principalmente, evitar o impulso de capturar Exception genérica para resolver tudo. Por outro lado, além de esconder problemas reais, isso mistura 400, 409, 422 e 500 como se fossem a mesma categoria.

Se a sua API também lida com autenticação e filtros, faz sentido comparar com JWT no Spring Security com Spring Boot: autenticação moderna passo a passo, porque nem todo 400 ou 401 nasce no mesmo ponto do pipeline da requisição. Por outro lado, entender onde a falha acontece ajuda a escolher o handler certo.

E se o endpoint persiste dados depois da entrada ser processada com sucesso, vale comparar com Guia completo de Spring Data JPA no Spring Boot sem dor. Por outro lado, o erro de JSON fica antes da camada de persistência, e separar essas responsabilidades ajuda a não confundir causa técnica com erro de banco.

Como tratar erro 400 JSON Spring Boot pensando em manutenção e contrato de API

Em projeto pequeno, qualquer solução parece suficiente. Por outro lado, em produção, o que pesa é consistência ao longo do tempo. Ao mesmo tempo, se hoje sua API responde com { message: “JSON inválido” } e amanhã muda para outro formato sem padrão, o cliente sente. Na prática, se o suporte não consegue correlacionar resposta, log e endpoint afetado, a operação sente também.

Por isso, tratar json invalido no spring boot como tratar erro 400 não é só capturar uma exceção. Por outro lado, é definir contrato de erro. Ao mesmo tempo, quais campos sua resposta sempre terá? Na prática, que mensagens são públicas e estáveis? Ainda assim, o que vai para log? Por isso, o que fica rastreável em observabilidade? Além disso, essa disciplina evita um monte de ruído depois.

Uma decisão sensata é manter mensagem amigável para o cliente e detalhes técnicos estruturados nos logs. Por outro lado, outra boa prática é testar payloads quebrados de forma explícita. Ao mesmo tempo, não apenas o caso feliz, mas vírgula sobrando, tipo incompatível, enum inválido e corpo vazio. Na prática, isso reduz surpresa quando a API está exposta para consumidores que você não controla.

Como tratar erro 400 json spring boot: referencias externas

Para validar detalhes de implementacao e aprofundar a configuracao, vale consultar a documentacao oficial do Spring Security, o guia de claims no JWT.io e a documentacao do Spring Boot.

FAQ

Como tratar erro 400 JSON Spring Boot com @RequestBody inválido?

O caminho mais comum é capturar HttpMessageNotReadableException em um @RestControllerAdvice e devolver uma resposta 400 com mensagem clara, como JSON malformado ou campo em formato inválido. Por outro lado, isso funciona melhor do que depender da mensagem padrão do framework.

Por que o Spring Boot lança HttpMessageNotReadableException?

Porque o framework tenta desserializar o corpo da requisição antes de chamar o método do controller. Por outro lado, se o Jackson não consegue converter o JSON em objeto Java, o Spring encapsula a falha nessa exceção e responde com 400.

Quando usar tratamento local no controller e quando usar @ControllerAdvice?

Tratamento local faz sentido quando um endpoint específico precisa de comportamento próprio. Por outro lado, para padronizar a API inteira, reduzir duplicação e melhorar manutenção, a escolha mais segura costuma ser @ControllerAdvice ou @RestControllerAdvice.

Conclusão

Quando o corpo chega quebrado, o Spring Boot não está sendo chato; ele está falhando no ponto certo, antes que dados inválidos atravessem a API. Por outro lado, o ajuste que realmente melhora a experiência é transformar esse 400 técnico em uma resposta útil, consistente e fácil de manter. Ao mesmo tempo, se a sua dúvida era essa validacao, a resposta mais equilibrada para API real é: usar handler global para HttpMessageNotReadableException, separar parse de validação e não expor mensagem interna do framework para o cliente.

Se houver uma exigência pontual de contrato, o tratamento local pode servir. Por outro lado, fora isso, centralização quase sempre ganha. Ao mesmo tempo, menos duplicação, menos inconsistência e menos tempo perdido tentando entender por que cada endpoint responde de um jeito.

Leitura complementar: aprofundar em como tratar exceções no spring boot com @controlleradvice e @exceptionhandler, aprofundar em responseentity no spring boot: quando usar, exemplos e erros comuns, comparar com Guia completo de testes no Spring Boot: evite falhas em produção, comparar com JWT no Spring Security com Spring Boot: autenticação moderna passo a passo e comparar com Guia completo de Spring Data JPA no Spring Boot sem dor. Por outro lado, os proximos passos sao validar esse fluxo no seu projeto, ajustar o caso de uso real e cobrir a implementacao com testes.

Deixe um comentário