409 Conflict
使い方
409 Conflict エラーが届いた場合、リクエスト自体は有効ですが、状態の不一致により完了できません。たとえば、クライアントがすでに削除された投稿に返信しようとした場合、ターゲットリソースはもはや互換性のある状態にありません。サーバーは競合を示すためにこのステータスを返します。
注意
409 Conflict は、リクエストの形式は正しいものの、現在のリソースの状態と競合していること(同時更新、重複作成)を意味します。422 Unprocessable Content は、リクエストボディが構文上は有効でも意味的に無効であること(検証ルールの不合格)を意味します。400 Bad Request は、リクエストそのものの形式が不正であること(解析できないボディ、無効なヘッダー)を意味します。409 は状態の競合、422 は検証、400 は構文についての話です。
クライアントは、問題をどう解決するかを判断するためにメッセージボディを調べることが期待されます。削除された投稿に返信する場合、クライアントはリクエストを取りやめる、以前のスレッドの削除を取り消す、以前のテキストを引用した新しいスレッドを作成する、といった選択をします。この判断は完全にクライアントの責任であり、サーバーが定める要件やポリシーに従います。
SEO への影響
Google のような検索エンジンは、409 ステータスを返す URL をインデックスしません。以前にインデックスされていた URL がこのステータスコードを返すようになると、検索結果から削除されます。
例
クライアントが「ID = 111」の既存のブログ投稿に「like」のタグを付けようとします。サーバーは、その投稿が凍結されており、返信やタグをもはや受け付けないことを示す応答を返します。
リクエスト
POST /blog/update?postid=111&flag=like HTTP/1.1
Host: example.com
レスポンス
HTTP/1.1 409 Conflict
Content-Type: text/html
Content-Length: 288
<html>
<head>
<title>Blog Entry Frozen</title>
</head>
<body>
<p>This post has been marked read-only. To post
a reply or tag, change the status and try
again.</p>
</body>
</html>
対処方法
409 Conflict は、リクエストがサーバー上のリソースの現在の状態と競合していることを意味します。
再試行の前に現在のリソースの状態を取得します。最新バージョンを取得するために GET リクエストを送信します。クライアントが最後にデータを読み取って以降、リソースの状態が変化しています。返された ETag やバージョンのフィールドを、クライアントが保持している値と比較します。
競合を検出するために条件付きヘッダーを使います。PUT や PATCH のリクエストに、現在の ETag 値を伴う If-Match を含めます。ETag が一致しなくなった場合サーバーはリクエストを拒否し、盲目的な上書きを防ぎます。このパターンは楽観的並行性制御と呼ばれます。
PUT /api/resource/42 HTTP/1.1
If-Match: "abc123"
Content-Type: application/json
{"name": "updated value"}
GET と PUT のあいだに別のクライアントがリソースを変更した場合、サーバーは 409 で応答します。
変更をマージするか、競合解決のロジックを実装します。クライアントが意図した変更を現在のサーバーの状態と比較します。last-write-wins、フィールド単位のマージ、またはエンドユーザーに差分の解決を促す、といったマージ戦略を適用します。ペイロードに version や revision のフィールドを持つ API では、更新リクエストに現在のバージョン番号を含めることがクライアントに求められます。
リソースの重複による競合を解消します。POST や PUT でリソースを作成する際、同じ一意識別子を持つリソースがすでに存在する場合、サーバーは 409 を返します。作成の前に既存のエントリを確認し、再試行時の重複作成を避けるために冪等性キーを使います。
データベースレベルの競合に対処します。データベースを背後に持つアプリケーションサーバーは、一意制約、外部キー制約、行レベルのロックが操作を妨げる場合に 409 を返します。レスポンスボディには具体的な制約名が含まれることがよくあります。再試行の前に、根本にあるデータの競合(キーの重複、親レコードの欠落、同時トランザクション)を解消します。
バージョン管理システムの場合は、最新バージョンを取得して変更を適用し直します。最新のリビジョンを取得し、差分を調整して、更新を再送信します。Git ベースの API(GitHub、GitLab)は、プッシュが上流の変更と競合する場合に 409 を返します。
競合の詳細についてレスポンスボディを調べます。サーバーは通常、不一致を説明する情報(競合しているフィールド、現在の値、期待されるバージョン)を含めます。このデータを使って正しい解決の道筋を判断します。
状態の更新を伴う再試行ロジックを実装します。自動化されたクライアントでは、最新のリソースの状態を取得し、意図した変更を適用し直して再送信する、という再試行ループを構築します。高い並行性の状況でサーバーに負荷をかけすぎないよう、指数バックオフを追加します。
コードリファレンス
.NET
HttpStatusCode.Conflict
Rust
http::StatusCode::CONFLICT
Rails
:conflict
Go
http.StatusConflict
Symfony
Response::HTTP_CONFLICT
Python3.5+
http.HTTPStatus.CONFLICT
Java
java.net.HttpURLConnection.HTTP_CONFLICT
Apache HttpComponents Core
org.apache.hc.core5.http.HttpStatus.SC_CONFLICT
Angular
@angular/common/http/HttpStatusCode.Conflict