Padrão de Erros - RFC 9457 (Problem Details for HTTP APIs)
Visão Geral
O PGCC-Service utiliza o padrão RFC 9457 (anteriormente RFC 7807) para respostas de erro em APIs HTTP. Este padrão define um formato estruturado e consistente para comunicar detalhes de erros aos consumidores da API.
O Spring Framework 6+ oferece suporte nativo a este padrão através da classe ProblemDetail, que é utilizada em toda a aplicação.
Estrutura da Resposta de Erro
Todas as respostas de erro seguem o formato application/problem+json:
{
"type": "about:blank",
"title": "Erro de Negócio",
"status": 400,
"detail": "Descrição detalhada do erro ocorrido",
"instance": "/pgcc/api/v1/endpoint-chamado",
"mnemonico": "CODIGO_DO_ERRO",
"timestamp": "2026-07-01T10:30:00.000Z"
}
Campos Padrão RFC 9457
| Campo | Tipo | Descrição |
|---|---|---|
type |
string | URI que identifica o tipo de problema (padrão: about:blank) |
title |
string | Resumo curto e legível do tipo de problema |
status |
integer | Código de status HTTP (ex: 400, 404, 500) |
detail |
string | Explicação específica sobre o erro ocorrido |
instance |
string | URI que identifica a ocorrência específica do problema |
Campos Customizados PGCC
| Campo | Tipo | Descrição |
|---|---|---|
mnemonico |
string | Código identificador único do erro no sistema (ex: VEICULO_NAO_ENCONTRADO) |
timestamp |
string | Data/hora ISO 8601 em que o erro ocorreu |
violacoes |
array | Lista de violações de validação (apenas para erros de validação) |
Hierarquia de Exceções
A aplicação define uma hierarquia de exceções de negócio baseada em ErrorResponseException do Spring:
ErrorResponseException (Spring)
├── PgccBusinessException (abstrata)
│ ├── PgccErrorNegocioException → HTTP 400 (Bad Request)
│ ├── PgccAccessDeniedException → HTTP 403 (Forbidden)
│ ├── PgccRegistroNaoEncontradoException → HTTP 404 (Not Found)
│ └── PgccErrorValidacaoJwt → HTTP 422 (Unprocessable Entity) - JWT e Hash
└── PgccErroNaoCobravelException → HTTP 5xx (Erro não cobrável)
Tipos de Erro e Códigos HTTP
1. Erro de Negócio (400 Bad Request)
Utilizado quando a requisição viola regras de negócio.
Exceção: PgccErrorNegocioException
throw new PgccErrorNegocioException(Erro.VEICULO_NAO_ENCONTRADO);
Resposta:
{
"type": "about:blank",
"title": "Erro de Negócio",
"status": 400,
"detail": "Veículo não encontrado",
"mnemonico": "VEICULO_NAO_ENCONTRADO",
"timestamp": "2026-07-01T10:30:00.000Z"
}
2. Acesso Negado (403 Forbidden)
Utilizado quando o usuário não tem permissão para acessar o recurso.
Exceção: PgccAccessDeniedException
throw new PgccAccessDeniedException(Erro.NAO_AUTORIZADO, "Usuário sem permissão");
Resposta:
{
"type": "about:blank",
"title": "Erro de Negócio",
"status": 403,
"detail": "Usuário sem permissão",
"mnemonico": "NAO_AUTORIZADO",
"timestamp": "2026-07-01T10:30:00.000Z"
}
3. Registro Não Encontrado (404 Not Found)
Utilizado quando um recurso específico não é encontrado no sistema.
Exceção: PgccRegistroNaoEncontradoException
throw new PgccRegistroNaoEncontradoException(Erro.CONSENTIMENTO_NAO_ENCONTRADO);
Resposta:
{
"type": "about:blank",
"title": "Erro de Negócio",
"status": 404,
"detail": "Não existe consentimento associado aos parâmetros informados",
"mnemonico": "CONSENTIMENTO_NAO_ENCONTRADO",
"timestamp": "2026-07-01T10:30:00.000Z"
}
4. Erro de Validação JWT/Hash (422 Unprocessable Entity)
Utilizado quando há falha na validação do token JWT ou do Hash. A requisição é sintaticamente correta, mas não pode ser processada devido a erros semânticos (ex: token expirado, hash já consumido, hash expirado, hash excluído, etc.).
Exceção: PgccErrorValidacaoJwt
Exemplos de uso:
// Validação de JWT
throw new PgccErrorValidacaoJwt(Erro.ERRO_JWT, "Token expirado");
// Validação de Hash
throw new PgccErrorValidacaoJwt(Erro.ERRO_HASH, "Hash já foi consumido: " + hashId);
Resposta (JWT):
{
"type": "about:blank",
"title": "Erro de Negócio",
"status": 422,
"detail": "Token expirado",
"mnemonico": "ERRO_JWT",
"timestamp": "2026-07-01T10:30:00.000Z"
}
Resposta (Hash):
{
"type": "about:blank",
"title": "Erro de Negócio",
"status": 422,
"detail": "Hash já foi consumido: 06cbf962-9921-44dd-b92e-6a5b0939972e",
"mnemonico": "ERRO_HASH",
"mensagemTecnica": "Hash já foi consumido: 06cbf962-9921-44dd-b92e-6a5b0939972e",
"timestamp": "2026-07-02T10:30:00.000Z",
"parametros": ["Hash já foi consumido: 06cbf962-9921-44dd-b92e-6a5b0939972e"]
}
5. Erro de Validação de Parâmetros (400 Bad Request)
Utilizado quando há violação de constraints de validação (Bean Validation).
Exceção: ConstraintViolationException
Resposta:
{
"type": "about:blank",
"title": "Validação Falhou",
"status": 400,
"detail": "executarCadastroConsentimento.cpf: CPF inválido",
"violacoes": [
{
"campo": "executarCadastroConsentimento.cpf",
"mensagem": "CPF inválido",
"valor": "123"
}
],
"timestamp": "2026-07-01T10:30:00.000Z"
}
6. Erro Não Cobrável (5xx)
Utilizado para erros de infraestrutura onde o hash de consulta não deve ser consumido (não cobrar do usuário).
Exceção: PgccErroNaoCobravelException
throw new PgccErroNaoCobravelException(502, "Serviço RENAVAM indisponível");
Resposta:
{
"type": "about:blank",
"title": "Erro Não Cobrável",
"status": 502,
"detail": "Serviço RENAVAM indisponível",
"timestamp": "2026-07-01T10:30:00.000Z"
}
7. Erro Interno do Servidor (500 Internal Server Error)
Utilizado para exceções não tratadas (fallback).
Resposta:
{
"type": "about:blank",
"title": "Erro Interno do Servidor",
"status": 500,
"detail": "Mensagem da exceção",
"timestamp": "2026-07-01T10:30:00.000Z"
}
Catálogo de Erros (Mnemônicos)
O enum Erro define todos os códigos de erro disponíveis no sistema:
| Mnemônico | Descrição |
|---|---|
PLACA_INVALIDA_AUSENTE |
Placa inválida ou ausente |
VEICULO_NAO_ENCONTRADO |
Veículo não encontrado |
AUTORIZACAO_NAO_ENCONTRADA |
Autorização não encontrada |
CONSENTIMENTO_PENDENTE |
Consentimento pendente de autorização do Titular |
CONSENTIMENTO_NEGADO_PELO_TITULAR |
Acesso negado pelo titular, o último consentimento foi negado |
CONSENTIMENTO_NAO_ENCONTRADO |
Não existe consentimento associado aos parâmetros informados |
CONSENTIMENTO_REVOGADO |
Acesso negado pelo titular, o último consentimento foi revogado |
TEMPLATE_NAO_ENCONTRADO |
Template não encontrado |
CONDUTOR_NAO_ENCONTRADO |
Condutor não encontrado |
INFRACAO_NAO_ENCONTRADA |
Infração não encontrada |
CERTIFICADO_NAO_ENCONTRADO |
Certificado Público não encontrado |
CNPJ_USUARIO_OBRIGATORIO |
CNPJ do Usuário não encontrado na requisição |
CNPJ_GCC_OBRIGATORIO |
CNPJ do GCC não encontrado na requisição |
GCC_NAO_ENCONTRADO |
GCC não encontrado para o CNPJ informado |
CONTROLADOR_NAO_ENCONTRADO |
Controlador não encontrado para o CNPJ informado |
ERRO_VALIDACAO_CERTIFICADO |
Não foi possível autorizar o certificado no momento |
ERRO_INESPERADO |
Ocorreu um erro inesperado |
Para a lista completa, consulte o enum
br.gov.serpro.denatran.pgcc.exception.Erro.
Como Lançar Exceções
Erro Simples (sem parâmetros)
throw new PgccErrorNegocioException(Erro.VEICULO_NAO_ENCONTRADO);
Erro com Parâmetros
throw new PgccErrorNegocioException(Erro.GCC_NAO_ENCONTRADO, cnpj);
// Resultado: "GCC não encontrado para o CNPJ informado: 12345678000199"
Escolhendo a Exceção Correta
| Situação | Exceção | HTTP Status |
|---|---|---|
| Violação de regra de negócio | PgccErrorNegocioException |
400 |
| Usuário sem permissão para o recurso | PgccAccessDeniedException |
403 |
| Recurso não encontrado no banco/sistema | PgccRegistroNaoEncontradoException |
404 |
| Token JWT inválido ou expirado | PgccErrorValidacaoJwt |
422 |
| Hash consumido, expirado, excluído ou inválido | PgccErrorValidacaoJwt |
422 |
| Erro em serviço externo (não cobrar consulta) | PgccErroNaoCobravelException |
5xx |
Tratamento Global de Exceções
O RestExceptionHandler é responsável por capturar todas as exceções e convertê-las para o formato RFC 9457:
@RestControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(PgccErrorNegocioException.class)
public ResponseEntity<ProblemDetail> handlePgccErrorNegocioException(PgccErrorNegocioException ex) {
log.warn("Erro de negócio: {}", ex.getBody().getDetail());
return ResponseEntity.status(ex.getStatusCode()).body(ex.getBody());
}
// ... outros handlers
}