Skip to content

Código de status HTTP · Erros do cliente (4xx)

Erro 409 Conflict

O erro 409 Conflict significa que o pedido não pôde ser aplicado porque conflita com o estado atual do recurso, e o cliente talvez consiga resolver o conflito e tentar de novo. As causas mais comuns são criar algo que já existe (e-mail ou nome duplicado) e salvar uma edição baseada numa versão desatualizada.

Dados sobre este código de status
Classe4xx, Erros do cliente
Definido emRFC 9110 §15.5.10
Pode ir para o cache por padrãoSó com Cache-Control ou Expires explícitos
Pode repetir o pedidoDepois de resolver o conflito, por exemplo buscando a versão mais recente e reaplicando a mudança
Cabeçalhos relevantes
  • ETag: devolva a versão atual para o cliente reler e tentar de novo

O que significa o 409

A RFC 9110, seção 15.5.10, diz que o 409 serve para conflitos com o estado atual do recurso que o usuário talvez consiga resolver, e que a resposta deve explicar o conflito o bastante para ele reconhecer a origem. O exemplo da RFC é um PUT com mudanças que conflitam com uma edição anterior feita por outra pessoa.

Sistemas reais usam assim. O Kubernetes responde 409 quando você atualiza um objeto com resourceVersion antigo ("the object has been modified; please apply your changes to the latest version") e quando tenta criar um que já existe. O Elasticsearch responde 409 em conflito de versão, e o Amazon S3 responde 409 BucketAlreadyExists quando o nome do bucket já está em uso.

Use 409 para conflito de estado, não para entrada inválida. Um e-mail malformado é 400 ou 422; um e-mail válido mas já cadastrado é 409, porque o mesmo pedido daria certo com outro estado do servidor.

Causas comuns

Se você está visitando o site

  • Você tentou se cadastrar com um e-mail ou nome de usuário que já existe.
  • Outra pessoa editou o mesmo registro enquanto você estava com ele aberto, e o seu salvamento partiu da versão antiga.
  • Você clicou duas vezes no botão de enviar e o segundo pedido tentou criar a mesma coisa de novo.

Se você administra o servidor

  • Uma restrição de unicidade no banco (e-mail, slug, ID externo) recusou o insert, e a API traduz isso em 409.
  • Concorrência otimista: o cliente enviou um número de versão ou ETag que não bate mais com o armazenado.
  • Uma transição de estado não permitida no momento, como cancelar um pedido que já foi enviado.
  • Operações no estilo Git, como enviar ou mesclar num branch que já avançou.

Como resolver

Se você está visitando o site

  • Recarregue a página para pegar a versão mais recente, refaça a alteração e salve de novo.
  • No cadastro, use outro e-mail ou tente entrar ou redefinir a senha da conta que já existe.

Se você administra o servidor

  • Devolva um corpo que diga o que conflitou (qual campo, qual versão atual) para o cliente resolver sem adivinhar.
  • No cliente, trate o 409 relendo o recurso, mesclando ou perguntando ao usuário, e tente de novo com a versão nova.
  • Use chaves de idempotência em endpoints de criação para um clique duplo devolver o primeiro resultado em vez de conflito.
  • Capture as violações de unicidade de forma explícita; deixá-las subir transforma um 409 limpo em 500.

Como enviar um 409

Express (Node.js)
app.put('/documents/:id', (req, res) => {
  res.set('ETag', '"v8"');
  res.status(409).json({ error: 'Document was changed by someone else', current_version: 8 });
});
Next.js App Router (route handler)
// app/documents/[id]/route.ts
export async function PUT() {
  return Response.json(
    { error: 'Document was changed by someone else', current_version: 8 },
    { status: 409, headers: { 'ETag': '"v8"' } }
  );
}
Go net/http
mux.HandleFunc("PUT /documents/{id}", func(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("ETag", `"v8"`)
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusConflict) // 409
	w.Write([]byte(`{"error":"Document was changed by someone else","current_version":8}`))
})
Python FastAPI
from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()

@app.put("/documents/{id}")
def update_document(id: str):
    return JSONResponse(
        status_code=409,
        content={"error": "Document was changed by someone else", "current_version": 8},
        headers={"ETag": '"v8"'},
    )

Costuma ser confundido com

409 vs 412
O 412 é a versão com pré-condição: o cliente mandou If-Match e o ETag não bateu. O 409 cobre conflitos que o servidor detecta sem cabeçalhos condicionais.
409 vs 422
O 422 indica que a entrada em si é inválida; o 409 indica entrada válida que conflita com o que já existe.
409 vs 400
O 400 é para pedidos malformados em qualquer situação; um pedido com 409 daria certo com outro estado do servidor.

Perguntas frequentes

E-mail duplicado deve retornar 409 ou 400?
O 409 se encaixa melhor: o pedido é válido, mas conflita com um registro existente. Algumas equipes preferem 422 ou 400 para não revelar quais e-mails estão cadastrados, uma troca razoável por privacidade em formulários de cadastro.
Qual a diferença entre 409 e 412?
O 412 Precondition Failed é disparado por um cabeçalho condicional, como If-Match, que não bateu. O 409 é o servidor detectando sozinho um conflito com o estado atual, por exemplo por um campo de versão no corpo.
O cliente pode repetir um 409 automaticamente?
Não às cegas: o mesmo pedido vai conflitar de novo. Repita depois de reler o recurso e reaplicar a mudança, o que alguns clientes fazem sozinhos em contadores ou mesclas simples.
Por que o Kubernetes devolve 409 Conflict?
Ou o objeto já existe na criação, ou você atualizou com um resourceVersion que não é mais o atual porque outra pessoa mudou o objeto. Busque o objeto mais recente, reaplique a mudança e atualize de novo.

Revisado em por Arielton Oberek.