マイクロサービスの数が増え、サービス間の呼び出しが多くなると、個々の処理は軽くても通信部分のコストが無視できなくなります。

JSONのシリアライズとデシリアライズ、HTTPのヘッダー、接続上でのリクエスト処理、サービス間で何度も繰り返されるデータ変換。1回あたりの差は小さくても、高頻度の内部通信では積み重なります。

そこで選択肢になるのが gRPC です。

gRPCはHTTP/2とProtocol Buffersを組み合わせ、サービス定義からクライアントとサーバーのコードを生成できるRPCフレームワークです。特に、内部マイクロサービス間の高頻度通信、ストリーミング、複数言語をまたぐシステムで採用されます。

さらにSpring Boot 4.1ではgRPCの公式サポートが追加され、従来のようにサードパーティー製Starterを前提にしなくても、Spring Bootの自動構成を使ってサーバーとクライアントを実装できるようになりました。

ただし、「RESTは遅く、gRPCは速い」と方式だけで結論づけることはできません。REST APIでもHTTP/2を利用でき、JSON以外のデータ形式も選べるためです。実際に性能差が出るのは、HTTP/2の多重化やProtobufによる小さなバイナリメッセージが、そのシステムの通信負荷に効く場合です。

この記事では、REST + JSONとgRPC + Protobufで通信処理がどう変わるのかを確認し、Spring Boot 4.1でgRPCを実装する方法まで扱います。

まず前提:RESTそのものが遅いわけではない

RESTとgRPCを比較するときによくある説明に、「RESTはHTTP/1.1なので毎回TCP接続を張り直す」「HTTP/1.1には接続の再利用がない」というものがあります。

HTTP/1.1は持続接続を標準でサポートしており、同じTCP接続を複数のリクエストで再利用できます。そのため、「HTTP/1.1ではリクエストごとにTCP接続を張り直す」という説明は誤りです。

また、RESTというアーキテクチャスタイル自体がHTTP/1.1やJSONを必須としているわけでもありません。REST APIをHTTP/2で提供することもできます。

この記事で比較する対象は、Web APIで一般的に使われる次の2構成です。

  • REST + JSON
  • gRPC + HTTP/2 + Protocol Buffers

RESTとgRPCそのものを抽象的に比較するのではなく、実際の通信方式とデータ表現の違いを見る必要があります。

HTTP/1.1とHTTP/2の違い

HTTP/1.1でも接続は再利用できますが、1本の接続上で複数のレスポンスを同時進行で処理する仕組みには制約があります。並列性を確保するために複数接続を使う実装も一般的です。

一方、gRPCが利用するHTTP/2は、1本の接続の中に複数のストリームを持ち、それぞれのリクエストとレスポンスを多重化できます。

項目HTTP/1.1HTTP/2
接続の再利用可能可能
1接続上の多重化基本的に不可可能
転送単位HTTPメッセージバイナリフレーム
ヘッダー圧縮なしHPACK
複数リクエストの並行処理複数接続などで対応1接続内で複数ストリームを処理

サービス間で短いRPCを大量に実行する場合、1本の接続上で複数RPCを並行処理できるため、接続数や接続管理に伴う処理を減らせます。

ただし、HTTP/2を使うだけでアプリケーション全体が速くなるわけではありません。処理時間の大半をデータベースや外部APIが占めている場合、通信プロトコルを変えても全体の応答時間はほとんど変わらないことがあります。

gRPCが通信オーバーヘッドを減らせる仕組み

gRPCはアプリケーションの業務処理そのものを高速化する技術ではありません。差が出るのは主に通信部分です。

たとえば、短いRPCを高頻度で実行するサービスでは、HTTP/2による多重化とProtobufによるバイナリ表現によって、接続管理、ヘッダー、シリアライズ、転送量にかかるコストを減らせる場合があります。

逆に、1回のリクエストで数百ミリ秒から数秒のデータベース処理を行うAPIでは、通信部分を数ミリ秒短縮しても全体性能への影響は小さくなります。

つまり「gRPCだから速い」のではなく、gRPCが減らせる通信コストが、そのシステムのボトルネックになっているときに性能差が出ると考える方が適切です。

HTTP/2の多重化を標準で使う

gRPCはHTTP/2を通信基盤として使います。

1つのChannelを継続利用し、その上で複数のRPCを並行して処理できます。リクエストごとに新しい接続を増やす必要がなく、1本の接続を複数RPCで共有できます。

REST APIでもHTTP/2は利用できますが、gRPCはRPCの通信モデルそのものがHTTP/2のストリームを前提として設計されている点が異なります。

Protocol Buffersでデータをバイナリ化する

gRPCでは通常、メッセージ形式として**Protocol Buffers(Protobuf)**を使います。

JSONでは、次のようにフィールド名を文字列として送ります。

{
  "id": 1001,
  "name": "Alice",
  "email": "[email protected]"
}

Protobufでは、.protoであらかじめフィールド番号と型を決め、その定義に従ってバイナリ形式へエンコードします。

message UserResponse {
  int64 id = 1;
  string name = 2;
  string email = 3;
}

通信のたびにidnameといったフィールド名を文字列として送る必要がないため、同じデータをJSONより小さく表現できるケースがあります。また、型が決まっているため、テキストを解析して値へ変換するJSONとは異なる処理経路になります。

ただし、転送量やシリアライズ時間の差はメッセージ構造、値、JSONライブラリ、圧縮設定によって変わります。「Protobufなら常に何%小さい」「gRPCなら常に何倍速い」という固定値はありません。性能が重要なシステムでは、実際に送るデータで測定する必要があります。

Protoファイルが通信契約になる

gRPCでは、サービスのメソッドとデータ構造を.protoファイルで定義します。

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.example.demo.proto";

service Simple {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  rpc StreamHello (HelloRequest) returns (stream HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

この定義からJava、Go、Python、C++、C#など各言語向けのコードを生成できます。

Javaの場合は、概念的には次の流れになります。

hello.proto

Protobuf / gRPCのコード生成

メッセージクラス + Stub + サーバー実装用の基底クラス

クライアントとサーバーが同じ契約で通信

クライアント側とサーバー側が同じProto定義から生成した型を使うため、契約と異なる型を渡した場合はJavaの型エラーとして検出できます。URLやJSON構造を手作業で合わせる方式とは、契約違反の見つかり方が異なります。

gRPCは4種類のRPCを使える

gRPCは単純なリクエスト・レスポンスだけでなく、ストリーミングを標準で扱えます。

方式通信主な用途
Unary RPC1リクエスト → 1レスポンス通常のサービス呼び出し
Server Streaming1リクエスト → 複数レスポンスイベント配信、ログ、逐次結果
Client Streaming複数リクエスト → 1レスポンスデータの連続送信、集約処理
Bidirectional Streaming双方向にストリームリアルタイム対話、継続的なデータ交換

特に双方向ストリーミングでは、クライアントとサーバーが互いに独立してメッセージを送り続けられます。

AIアプリケーションで逐次結果を返す場合や、リアルタイム性の高いサービス間通信など、一問一答のリクエスト・レスポンスだけでは表現しにくい処理に使えます。

Spring Boot 4.1でgRPCが公式サポートされた

Spring Boot 4.1では、gRPCのクライアントとサーバーをSpring Bootの自動構成で扱えるようになりました。

2026年8月時点ではSpring Boot 4.1系が安定版として提供されており、gRPC向けには次の公式Starterがあります。

用途Starter
サーバーspring-boot-starter-grpc-server
クライアントspring-boot-starter-grpc-client
サーバーテストspring-boot-starter-grpc-server-test
クライアントテストspring-boot-starter-grpc-client-test

以前はSpring BootでgRPCを使うときにサードパーティー製Starterを採用する構成も多くありました。Spring Boot 4.1では公式Starterが用意され、Spring Bootの依存管理と自動構成の中でgRPCサーバーとクライアントを設定できます。

Mavenで依存関係を追加する

サーバー側では次のStarterを使います。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-grpc-server</artifactId>
</dependency>

クライアントも同じアプリケーションに含める場合は、クライアントStarterも追加します。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-grpc-client</artifactId>
</dependency>

Spring BootのBOMまたはspring-boot-starter-parentを使っている場合、gRPC関連依存のバージョンはSpring Boot側で管理されます。

ProtoからJavaコードを生成する

.protoはそのままJavaから呼び出せないため、ビルド時にJavaコードへ変換します。

Spring Boot 4.1はMaven向けにio.github.ascopes:protobuf-maven-pluginの依存管理と設定を提供しています。spring-boot-starter-parentを使う場合は、次のようにプラグインを追加し、.protosrc/main/protoへ置く構成にできます。

<build>
    <plugins>
        <plugin>
            <groupId>io.github.ascopes</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Gradleではcom.google.protobufプラグインを適用すると、Spring Boot Gradle PluginがProtobuf関連の設定に反応します。

Spring Boot 4.1でgRPCサーバーを実装する

src/main/proto/hello.protoからコードを生成したとします。

生成されたSimpleGrpc.SimpleImplBaseを継承し、@GrpcServiceを付けてSpring Beanとして登録します。

package com.example.demo.service;

import com.example.demo.proto.HelloReply;
import com.example.demo.proto.HelloRequest;
import com.example.demo.proto.SimpleGrpc;
import io.grpc.stub.StreamObserver;
import org.springframework.grpc.server.service.GrpcService;

@GrpcService
public class GrpcServerService extends SimpleGrpc.SimpleImplBase {

    @Override
    public void sayHello(
            HelloRequest request,
            StreamObserver<HelloReply> responseObserver) {

        HelloReply reply = HelloReply.newBuilder()
                .setMessage("Hello => " + request.getName())
                .build();

        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }

    @Override
    public void streamHello(
            HelloRequest request,
            StreamObserver<HelloReply> responseObserver) {

        // サーバーから複数のレスポンスを順番に返す
        for (int i = 0; i < 5; i++) {
            HelloReply reply = HelloReply.newBuilder()
                    .setMessage("Hello " + request.getName() + " (message " + i + ")")
                    .build();
            responseObserver.onNext(reply);
        }

        responseObserver.onCompleted();
    }
}

@GrpcServiceを付けたクラスはSpringのコンポーネントスキャン対象になります。生成されたgRPCサービスはBindableServiceを実装しているため、Spring BootがgRPC Serverへ登録します。

サーバーのポートを設定する

application.ymlでは次のように設定できます。

spring:
  grpc:
    server:
      port: 9090

NettyベースのgRPC Serverでは9090がデフォルトポートなので、変更しない場合は明示的な指定は必須ではありません。

Spring Boot 4.1でgRPCクライアントを実装する

クライアントでは、生成されたStubをSpring Beanとしてインポートします。

Spring Boot 4.1の公式な書き方では、起動クラスなどに@ImportGrpcClientsを付けます。

package com.example.demo;

import com.example.demo.proto.SimpleGrpc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.grpc.client.ImportGrpcClients;

@SpringBootApplication(proxyBeanMethods = false)
@ImportGrpcClients(
        target = "hello",
        types = SimpleGrpc.SimpleBlockingStub.class
)
public class GrpcClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(GrpcClientApplication.class, args);
    }
}

ここでhelloは接続先そのものではなく、論理的なChannel名として使います。

application.ymlで実際の接続先を設定します。

spring:
  grpc:
    client:
      channel:
        hello:
          target: static://localhost:9090

本番環境では接続先をlocalhost:9090へ固定せず、環境変数やサービスディスカバリーからホスト名とポートを渡せるようにします。

たとえば環境変数を使うなら次のようにできます。

spring:
  grpc:
    client:
      channel:
        hello:
          target: static://${HELLO_GRPC_HOST:localhost}:${HELLO_GRPC_PORT:9090}

Stubを注入して呼び出す

@ImportGrpcClientsによって作成されたStubは、通常のSpring Beanと同じように注入できます。

package com.example.demo.client;

import com.example.demo.proto.HelloReply;
import com.example.demo.proto.HelloRequest;
import com.example.demo.proto.SimpleGrpc;
import org.springframework.stereotype.Component;

@Component
public class HelloClient {

    private final SimpleGrpc.SimpleBlockingStub simpleStub;

    public HelloClient(SimpleGrpc.SimpleBlockingStub simpleStub) {
        this.simpleStub = simpleStub;
    }

    public String sayHello(String name) {
        HelloRequest request = HelloRequest.newBuilder()
                .setName(name)
                .build();

        HelloReply response = simpleStub.sayHello(request);
        return response.getMessage();
    }
}

RESTクライアントのようにURL、HTTPメソッド、レスポンスJSONの型を個別に指定するのではなく、Protoから生成したStubのメソッドを呼び出します。

grpcurlで確認する場合の注意点

gRPCの動作確認にはgrpcurlを利用できます。

サーバーのリフレクション機能を使ってProtoファイルを指定せずに呼び出したい場合、Spring Bootではio.grpc:grpc-servicesがクラスパスにあるときにgRPC Reflectionが自動構成されます。

必要であれば次の依存を追加します。

<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-services</artifactId>
</dependency>

Reflectionが有効で、平文通信のローカル環境であれば、たとえば次のように確認できます。

grpcurl -plaintext \
  -d '{"name":"World"}' \
  localhost:9090 Simple/SayHello

想定されるレスポンスは次のようになります。

{
  "message": "Hello => World"
}

本番環境ではTLSや認証を含めた構成が必要になるため、-plaintextをそのまま使う想定ではありません。

gRPCを使うメリット

通信部分のCPU負荷や転送量を減らせる場合がある

HTTP/2の多重化とProtobufによるバイナリメッセージは、短いRPCを高頻度に実行する構成で効果が出やすい仕組みです。

たとえば、小さなメッセージを短い間隔で大量にやり取りする内部サービスでは、REST + JSONと比べて、接続管理、JSONの解析、転送量に使う処理が減る場合があります。その結果としてレイテンシやCPU使用量が下がることがあります。

一方、処理時間の大半をデータベースや外部APIが占めるサービスでは、通信方式を変えても全体性能はほとんど変わりません。gRPC導入前後の差は、自分たちの実際のワークロードで測定する必要があります。

複数言語のサービスを同じ契約でつなげる

ProtoはJava専用ではありません。

たとえば、次のようにサービスごとに言語が異なるシステムでも、同じサービス定義からそれぞれのクライアントコードを生成できます。

Java API Gateway
      ↓ gRPC
Goの高性能サービス
      ↓ gRPC
Pythonのデータ処理サービス

HTTP APIでもOpenAPIなどを使って同様の契約管理はできますが、gRPCではProtoからクライアントとサーバーのコードを生成する流れが標準の開発モデルに含まれています。

ストリーミングをRPCとして扱える

サーバーストリーミング、クライアントストリーミング、双方向ストリーミングを同じサービス定義の中で扱えます。

リアルタイムイベント、逐次処理、AIのストリーミング応答などでは、通信方式を別の仕組みに分けず、RPC定義の中でストリームとして表現できます。

型の変更を生成コードへ反映できる

Protoのフィールドには型と番号があります。

API契約を変更すると、その変更がクライアントとサーバーの生成コードにも反映されます。Javaなど静的型付け言語では、古い型のまま呼び出しているコードをコンパイルエラーとして検出できる場合があります。

ただし、Protoにも互換性ルールがあります。既存フィールド番号の再利用を避けるなど、スキーマを長期運用するための設計は必要です。

gRPCの注意点

ブラウザから標準gRPCを直接呼び出せない

通常のgRPCは、ブラウザのJavaScriptからそのまま利用する用途には向いていません。

ブラウザでは標準gRPCが必要とするHTTP/2の機能をネットワークAPIから直接扱えないため、gRPC-Webなどを使い、プロキシまたは対応サーバーを介して接続します。

そのため、不特定多数のWebクライアントへ直接公開するAPIでは、追加の中継層を必要としないREST + JSONの方が構成を単純にできます。

JSONのように本文をそのまま読めない

Protobufはバイナリ形式です。

REST + JSONのようにブラウザやcurlでレスポンス本文をそのまま確認することはできません。内容を確認するにはgrpcurl、IDEのgRPCクライアント、サーバーリフレクションなどを使います。

Protoとコード生成の運用が増える

gRPCを導入すると、アプリケーションコードだけでなくProtoの管理、コード生成、互換性の維持が開発フローに加わります。

小規模なCRUD APIで通信性能にも困っていない場合、これらの追加工程に対して得られる効果が小さいことがあります。

Kubernetesでは長寿命Channelを意識する

gRPCはHTTP/2のChannelを長時間再利用します。

Kubernetes上で1つの接続が特定Podへ長時間向いたままになる構成では、HTTPリクエストごとのL4ロードバランシングと同じ感覚では分散しないことがあります。

この場合は、名前解決とプロキシの構成に応じて、クライアントサイドのロードバランシング、Headless Service、サービスディスカバリー、Service Meshなどを選びます。

REST + JSONとgRPCの使い分け

選択基準は「どちらが優れているか」ではなく、APIを誰が呼ぶのか、ストリーミングが必要か、通信部分が性能上のボトルネックか、Protoとコード生成を運用できるか、という条件です。

観点REST + JSONgRPC + Protobuf
ブラウザ・外部公開ブラウザから直接利用できるgRPC-Webなどの追加対応が必要
人間が読むデータJSON本文をそのまま確認できる内容確認に専用ツールが必要
サービス間の高頻度通信HTTP/2などを使って対応可能HTTP/2多重化とProtobufを標準利用
ストリーミングSSEやWebSocketなど別方式を使う場合がある4種類のRPCとして標準対応
型付き契約OpenAPIなどで実現可能Protoと生成コードを使用
多言語コード生成利用するツールによるProtoから各言語向けコードを生成
追加する開発工程一般的なWeb APIでは少ないProto管理とコード生成が加わる

gRPCを選ぶ理由があるケース

  • マイクロサービス間で短い呼び出しを高頻度に実行する
  • Java、Go、Pythonなど複数言語のサービスを共通契約でつなぎたい
  • サーバーストリーミングや双方向ストリーミングが必要
  • APIの型をProtoで管理し、生成コードを中心に開発したい

REST + JSONを選ぶ理由があるケース

  • ブラウザや外部開発者が直接利用する公開API
  • 単純なCRUD中心で、通信性能が問題になっていない
  • JSONをそのまま確認できることを重視する
  • Protoとコード生成の工程を追加する必要がない

gRPCはRESTを置き換えるものではない

gRPCの価値は、「RESTより新しいから使う」ことではありません。

HTTP/2の多重化とProtobufによって通信オーバーヘッドを減らせること、Protoからコードを生成できること、ストリーミングをRPCとして定義できることが主な特徴です。これらは、短いRPCを高頻度に実行する内部サービスや、ストリーミングを必要とするシステムで特に意味を持ちます。

一方、REST + JSONはブラウザから直接利用でき、JSON本文をそのまま確認でき、対応するツールやサービスも多くあります。RESTもHTTP/2を利用できるため、「HTTP/1.1だからRESTは遅い」という説明は、RESTの性質とHTTP/1.1の性質を混同しています。

Spring Boot 4.1では公式Starterと自動構成が追加され、Java/SpringのプロジェクトでサードパーティーStarterを選定せずにgRPCを導入できるようになりました。

すでにREST APIで性能上の問題がないシステムを一括で置き換える理由はありません。通信回数が多い内部経路や、ストリーミングを必要とするサービスだけをgRPC化すれば、変更範囲を限定できます。

性能を理由に導入する場合は、一般的なベンチマークの数字ではなく、実際に使うペイロード、同時接続数、ネットワーク、TLS設定、実装条件をそろえた計測結果で判断します。

参考資料