💭

JavaでPATCH APIを表現する

2025/01/08に公開

はじめに

JavaでREST APIをつくってるとPATCHで困ったりしたことありませんか?
PATCHリクエストはリソースの部分更新を行う際に使われるメソッドですね。

https://developer.mozilla.org/ja/docs/Web/HTTP/Methods/PATCH

PUTリクエストとは異なり、完全にリソースの置き換えではなく、リクエストボディに含まれている属性のみを更新するものになります。

例えば以下のようなuserというリソースでニックネームだけをPATCHで更新したいとおもうと、

ID 名前 ニックネーム
1 Bob Bobby

こういったリクエストが期待されます。
[例]ニックネームをBobbieにしたい

Path: /users/1
Method: PATCH
{
  nickName: "Bobbie"
}

[例]ニックネームを消したい

Path: /users/1
Method: PATCH
{
  nickName: null
}

今回はJavaで上記のようなリクエストを受け付ける際に、リクエストボディ(Json)でプロパティがそもそもないのか(更新対象ではない)、プロパティはあるけど値がnullなのか(nullで更新したい)をどう切り分けるんだ?と思ったので同じお悩みの方がいたらぜひ参考にしていただけたら幸いです。

対象読者

  • Javaを使用したことがある方
  • Spring BootなどメジャーなWebフレームワークの経験がある方
  • REST APIを実装している方

技術スタック

  • Java(openjdk 17.0.2)
  • Spring Boot(3.4.0)
  • Lombok

概要

JavaでREST APIを実装する際はSpring Bootを始めとしたフレームワークを使用することが多いと思います。

こういったモダンなフレームワークでは基本的に、jacksonなどを使用したJson<->POJOのマッピング機能が提供されており、冒頭で例としてあげたリクエストボディを以下のようなオブジェクトに自動でマッピングしてくれます。

PatchUserRequest.java
@Data
public class PatchUserRequest {

    private String name;

    private String nickName;
}

Jsonマッピングを検証してみる

  1. nameを更新したい
json
{
  "name":"Alice"
}
java
PatchUserRequest(name="Alice",nickName=null)
  1. nickNameをnullで更新したい(ニックネームを消したい)
json
{
  "nickName":null
}
java
PatchUserRequest(name=null,nickName=null)

これでは1と2でnickNameをnullで更新したらいいのか、更新しちゃだめなのか区別がつきませんね。。

対策案(とっても簡単)

Optionalで包んであげるだけ

@Data
public class PatchUserRequest {

    private Optional<String> name;

    private Optional<String> nickName;
}

再検証

  1. nameを更新したい
json
{
  "name":"Alice"
}
java
PatchUserRequest(name=Optional[Alice], nickName=null)
  1. nickNameをnullで更新したい(ニックネームを消したい)
json
{
  "nickName":null
}
java
PatchUserRequest(name=null, nickName=Optional.empty)

これならnullのときはリクエストにない、Optional.emptyのときはリクエストにnullとして存在する。という判断ができます!

最後に

今回はSpringBootを使用して検証してみましたが、Jacksonを使用したPOJOマッピングであれば基本的にこの方法でいけそうです。Jakartaとか。
ちょっと探ってみた中では個人的にこれが一番しっくり、というか楽にできました。フレームワークなのでリクエストのインターセプター系でいい感じにできるかなと思いましたが、意外といいやり方がなかった。。。
こうすればいいじゃんっていうのがあったらぜひ教えてください!

Discussion