Ir para o conteúdo principal

Guias / Códigos de status HTTP explicados

409 Conflict: o que significa e como corrigir

Um 409 Conflict significa que o servidor entendeu a requisição, mas não consegue executá-la porque ela entra em conflito com o estado atual do recurso de destino, por exemplo ao criar algo que já existe ou ao salvar por cima de uma versão mais nova. A RFC 9110 o usa para casos em que o usuário talvez consiga resolver o conflito e enviar a requisição de novo.

O que significa 409 Conflict

A RFC 9110, seção 15.5.10, define o 409 como a resposta a uma requisição que não pôde ser concluída por causa de um conflito com o estado atual do recurso de destino. O código serve para situações em que o usuário talvez consiga resolver o conflito e reenviar. O servidor DEVERIA enviar um conteúdo que explique a origem do conflito. A RFC cita a requisição PUT como o lugar mais provável de um conflito: se o servidor rastreia versões e o PUT traz mudanças que conflitam com uma alteração anterior feita por outra pessoa, o servidor pode responder 409 e colocar no corpo o que o cliente precisa para mesclar as duas versões.

O status está na classe 4xx, mas a requisição em si não é o problema. A sintaxe está correta e os valores são válidos. O que está errado é a combinação entre a requisição e o estado do recurso naquele momento. Isso tem um resultado prático: a mesma requisição pode funcionar depois, quando o estado mudar ou quando o cliente mudar o que envia. Um 422 é diferente, porque os mesmos dados inválidos falham sempre.

Como o erro aparece

Os navegadores não têm uma página especial para o 409. Na maioria das vezes é uma resposta de API por trás de um botão ou formulário, então as pessoas o encontram como uma mensagem como “já existe”, “conflito” ou “este item foi alterado por outra pessoa”. Desenvolvedores o veem como uma linha vermelha com status 409 no painel Network das ferramentas de desenvolvedor do navegador. O corpo da resposta geralmente traz a explicação. Pela linha de comando:

curl -i -X POST https://api.example.com/v1/users   -H "Content-Type: application/json"   -d '{"email": "[email protected]", "name": "Ann"}'

HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{"type": "https://api.example.com/problems/email-in-use",
 "title": "Email already registered",
 "status": 409,
 "detail": "A user with this email address already exists."}

O corpo segue a RFC 9457, “Problem Details for HTTP APIs”, que define o formato application/problem+json. Uma requisição GET que devolve 409 é rara. Quando uma resposta 409 tem o corpo vazio, o Chrome mostra a página genérica “This page isn't working” (esta página não está funcionando) com o texto “HTTP ERROR 409”.

409 versus 400, 412, 422 e 428

Cinco códigos de status respondem “a requisição está errada de algum modo”, e algumas APIs os confundem. As diferenças vêm da RFC 9110 e, no caso do 428, da RFC 6585.

StatusO que o servidor está dizendoGatilho típicoO que o cliente faz
400 Bad RequestNão consigo processar isto: sintaxe malformada ou enquadramento de mensagem inválido.JSON quebrado, cabeçalhos ruins, uma linha de requisição corrompida.Corrige o formato da requisição.
409 ConflictA requisição é válida, mas conflita com o estado atual do recurso.Uma criação duplicada, uma versão antiga no corpo, excluir uma pasta que não está vazia.Lê o estado, resolve o conflito e envia de novo.
412 Precondition FailedUma condição nos cabeçalhos da sua requisição avaliou como falsa.If-Match ou If-Unmodified-Since não bate mais; If-None-Match: * num recurso que já existe.Busca a versão atual e o novo ETag e tenta de novo.
422 Unprocessable ContentA sintaxe está certa, mas não consigo processar as instruções que estão dentro.Um valor falha na validação ou numa regra de negócio.Muda os dados. Os mesmos dados falham de novo.
428 Precondition RequiredEste servidor exige uma requisição condicional.Um PUT sem If-Match numa API que o exige, para evitar atualizações perdidas.Adiciona o cabeçalho condicional.

Para escolher entre eles: se a requisição nunca pode funcionar do jeito que está escrita, use 400 ou 422. Se ela está correta mas depende de um estado que está errado agora, use 409. Se o cliente disse explicitamente “só faça isso se o recurso ainda estiver na versão X”, use 412.

O que causa um 409 Conflict

  • Criar algo que já existe. Um e-mail, nome de usuário, slug, número de pedido ou nome de arquivo único que já está em uso.
  • Salvar por cima de uma versão mais nova. Duas pessoas, ou duas abas do navegador, editam o mesmo registro, e o segundo salvamento parte de uma cópia antiga.
  • Uma ação que o estado atual não permite. Cancelar um pedido que já foi enviado, publicar um post que já está publicado ou excluir uma pasta que ainda tem arquivos dentro.
  • Um pai ausente. Criar um arquivo ou pasta dentro de uma pasta que não existe. Servidores WebDAV respondem assim.
  • Requisições duplicadas ou paralelas. Um clique duplo num botão de envio, ou uma nova tentativa depois de um timeout quando a primeira requisição na verdade tinha funcionado.
  • Um nome já usado num namespace compartilhado. Nomes de bucket, de repositório e de projeto são únicos em um serviço inteiro.

Onde você encontra o 409 na prática

Estes são casos documentados de sistemas reais. Eles mostram como o significado de “conflito” varia de uma API para outra.

  • WebDAV (RFC 4918). O MKCOL devolve 409 quando faltam coleções intermediárias, e o servidor não deve criá-las automaticamente. Um PUT que criaria um recurso sem uma coleção pai deve falhar com 409, e COPY e MOVE o usam quando falta o pai do destino.
  • Amazon S3. O CreateBucket devolve 409 com BucketAlreadyExists quando o nome já está em uso, porque o namespace de buckets é compartilhado por todos os usuários. Ele também devolve 409 com BucketAlreadyOwnedByYou quando você já é dono do bucket, em todas as Regiões exceto Norte da Virgínia (us-east-1), onde devolve 200 OK.
  • API do Kubernetes. Uma atualização com um resourceVersion desatualizado recebe 409 Conflict. O servidor da API usa isso para detectar atualizações perdidas.
  • CouchDB. 409 significa que já existe um documento com esse ID, ou que a revisão enviada não é a mais recente.
  • Stripe. 409 significa que a requisição conflita com outra requisição, talvez porque a mesma chave de idempotência está em uso.
  • GitHub. Fazer o merge de um pull request pela API devolve 409 se você passou um sha e o head do pull request não bate com ele. O mesmo endpoint usa 405 quando o merge não pode ser feito e 422 para falhas de validação.
  • GitLab. 409 significa que já existe um recurso conflitante. A API dele usa 412 para um If-Unmodified-Since que falhou num delete.

Como corrigir um 409 se você é o cliente

  1. Leia o corpo da resposta. Uma boa API diz o que conflita: qual campo, qual versão, qual recurso existente.
  2. Em caso de duplicata, busque o recurso existente. Se for a mesma coisa que você tentou criar, o seu objetivo já foi atingido, então trate a chamada como concluída. Se for outra coisa, escolha outro nome ou chave.
  3. Em caso de conflito de edição, busque a versão atual. Mescle a sua mudança nela, ou aplique a sua mudança de novo por cima dela, e envie a requisição com o novo número de versão ou ETag. Nunca sobrescreva às cegas, porque isso descarta o trabalho da outra pessoa.
  4. Em caso de conflito de estado, mude o estado primeiro. Crie a pasta pai, termine ou cancele a operação pendente ou esvazie a pasta, e só então repita a requisição.
  5. Evite envios repetidos. Desative o botão de envio depois do primeiro clique. Em requisições do tipo pagamento, envie uma chave de idempotência para que uma nova tentativa seja reconhecida como a mesma requisição.
  6. Tente de novo só depois que algo mudou. Repetir a mesma requisição às cegas normalmente recebe o mesmo 409. A exceção é um conflito que vem de um lock ou de uma operação em andamento, em que uma espera curta e uma nova tentativa podem funcionar.

Se você é um visitante num app web, os passos são mais curtos. Recarregue a página e confira se o seu item já existe ou se outra pessoa o alterou. Não aperte o botão várias vezes seguidas. Se você edita junto com outras pessoas, copie o seu texto, recarregue e aplique-o sobre a nova versão. Se o erro continuar, conte ao suporte do site o que você fez e em que horário.

Como devolver o 409 corretamente se você constrói a API

Um 409 só é útil se o cliente consegue saber o que conflita e o que fazer. Estes padrões cobrem os casos usuais.

Criação duplicada

Deixe o banco de dados garantir a unicidade e traduza o erro dele. Checar antes com um SELECT e inserir depois deixa uma brecha em que duas requisições paralelas passam pela checagem. Este exemplo usa Express e o pacote pg; o PostgreSQL reporta uma violação de unicidade com o código 23505 (o MySQL usa o erro 1062, e o SQL Server usa 2627 e 2601):

app.post('/v1/users', async (req, res, next) => {
  try {
    const { rows } = await db.query(
      'INSERT INTO users (email, name) VALUES ($1, $2) RETURNING id',
      [req.body.email, req.body.name]
    );
    res.status(201).location(`/v1/users/${rows[0].id}`).json({ id: rows[0].id });
  } catch (err) {
    if (err.code === '23505') {   // unique_violation
      return res.status(409).type('application/problem+json').json({
        type: 'https://api.example.com/problems/email-in-use',
        title: 'Email already registered',
        status: 409,
        detail: 'A user with this email address already exists.',
      });
    }
    next(err);
  }
});

Em formulários públicos de cadastro, essa resposta diz ao chamador quais endereços têm conta. Se isso importa para o seu produto, responda do mesmo jeito quer o endereço exista ou não, e envie o resultado real por e-mail.

Conflitos de versão

Dê a todo registro editável um número de versão e torne a atualização condicional a ele:

UPDATE documents
SET    body = $1, version = version + 1
WHERE  id = $2 AND version = $3;

Quando o comando altera zero linhas, outra pessoa salvou primeiro. Devolva 409 com a versão atual no corpo, para que o cliente possa mesclar. Use esse padrão quando a versão viaja no corpo da requisição.

Requisições condicionais com ETag

A forma padrão de evitar atualizações perdidas usa os próprios cabeçalhos do HTTP. O servidor envia um ETag, o cliente o devolve em If-Match, e uma divergência é um 412, não um 409:

GET /v1/documents/42
HTTP/1.1 200 OK
ETag: "7"

PUT /v1/documents/42
If-Match: "7"

HTTP/1.1 412 Precondition Failed

Para fazer todo cliente enviar uma condição, responda a um PUT sem If-Match com 428 Precondition Required. As APIs reais nem todas seguem essa divisão. O Kubernetes responde um resourceVersion desatualizado com 409, e o GitLab responde um If-Unmodified-Since que falhou com 412. Escolha uma regra, aplique-a em todo lugar e documente-a.

O corpo da resposta

Use o formato de problem details: um URI em type que identifica o tipo de conflito, um title curto, o status e um detail que nomeia o campo, a versão ou o recurso. Acrescente o ID ou a URL do recurso conflitante quando o chamador pode vê-lo. A RFC 9110 pede exatamente isso: informação suficiente para o usuário reconhecer a origem do conflito.

Como aparece visto de fora

Uma verificação de monitoramento normalmente lê uma página ou um endpoint de saúde, e esses não conflitam com nada, então um 409 no resultado de uma verificação é incomum e merece um alerta. Três padrões são comuns:

  • Uma verificação que cria algo. Se uma verificação envia um POST com um payload fixo, ela passa uma vez e depois recebe 409 em toda execução, porque o item agora existe. Use valores únicos por execução ou verifique um endpoint somente de leitura.
  • 409 depois de um deploy. Uma nova versão do app pode aplicar uma regra de unicidade ou de estado que a antiga não aplicava. Compare o resultado da verificação antes e depois do lançamento.
  • Resultados diferentes em locais diferentes. Quando os dados são replicados ou só eventualmente consistentes, dois locais podem ver estados diferentes e receber códigos de status diferentes. Compare os resultados por local antes de suspeitar da rede.

A verificação HTTP requisita uma URL de vários locais e informa o código de status de cada um, para você ver de uma vez se o 409 é o mesmo em todos. A HostTracker roda verificações a partir de mais de 300 pontos de verificação em 158 cidades e envia alertas por e-mail, SMS, chamada de voz, Slack, Telegram e mais quando uma verificação falha.

Perguntas frequentes

O 409 é um erro do cliente ou do servidor?

Está na classe 4xx, então a resposta diz que a requisição não pode funcionar do jeito que foi enviada. O servidor está saudável. O que precisa mudar costuma ser o estado do recurso, por exemplo um registro existente que precisa ser buscado ou removido, ou os dados que o cliente envia.

Um e-mail duplicado deve devolver 409 ou 422?

Os dois são usados. O 409 combina quando a causa é um recurso existente: a requisição está boa, mas o estado de destino dela conflita com ele. O 422 combina quando o valor quebra uma regra que não depende de outros registros, como um endereço malformado. Escolha um para conflitos de unicidade e aplique-o em toda a API.

Um conflito de edição é 409 ou 412?

Se o cliente enviou If-Match ou If-Unmodified-Since, a RFC 9110 aponta para o 412. Se o conflito é descoberto por um número de versão no corpo ou pelo estado do registro, o 409 é a resposta usual. A seção sobre requisições condicionais acima cita APIs reais que fazem cada escolha.

Devo repetir uma requisição que recebeu 409?

Não sem mudar alguma coisa. Busque o estado atual, resolva o conflito e envie a requisição de novo. Uma repetição sem nenhuma mudança normalmente recebe a mesma resposta. Um conflito causado por um lock ou por uma operação em execução pode passar depois de uma espera curta.

DELETE ou GET podem devolver 409?

O DELETE pode: excluir uma pasta que ainda tem arquivos, ou um registro de que outros registros dependem, é um conflito de estado. O GET raramente, porque ler não muda nada. Se um GET devolve 409, olhe como o servidor trata essa requisição.

Um PUT numa URL existente causa um 409?

Não, não só por isso. O PUT substitui o recurso naquela URL, então “já existe” não é um conflito. Um 409 aparece se o PUT conflita com outra coisa, como uma versão mais nova, um pai ausente ou um valor único usado em outro lugar.

Um 409 pode fazer uma página ser removida do Google?

Se uma página que os rastreadores buscam devolve 409, ela não será indexada. A documentação do Google diz que ele não indexa URLs que devolvem um código de status 4xx e remove as já indexadas. Isso importa para páginas, não para endpoints de API que os mecanismos de busca nunca requisitam.

Fontes e leitura adicional

Verificar agora

Faça a verificação gratuita no seu próprio site, sem precisar de conta.

HTTP check

Monitorar permanentemente

Seja avisado no momento em que algo quebrar: o HostTracker verifica de mais de 300 localidades e notifica você por e-mail, SMS, Slack, Telegram e mais.

Recursos do HostTracker

Mais nesta seção: Códigos de status HTTP explicados