java.sql.SQLException: Operation not allowed after ResultSet closed は、すでに閉じられた ResultSet から、next() や getString() で値を読もうとしたときのエラーです。 この形は MySQL Connector/J のメッセージで、PostgreSQL のドライバでは This ResultSet is closed. と表示されました。

自分で rs.close() を書いていなくても、次の2つで ResultSet は閉じられます。

原因 よくある書き方 直し方
① 同じ Statement で、別の SQL を実行した ループの中で、外側と同じ stmt で executeQuery・executeUpdate 内側の SQL は別の Statement(PreparedStatement)で実行
② Statement を閉じた try-with-resources の中で作った ResultSet を、メソッドから return した ResultSet ではなく、読み終えた値(List など)を返す

Java の公式ドキュメント(Statement)では、Statement の実行メソッドは、その Statement で開いている ResultSet を自動で閉じる、Statement を閉じるとその ResultSet も閉じると説明されています。

確認環境:OpenJDK 21、PostgreSQL 16.15 と pgJDBC 42.7.2、MySQL 8.0.46 と MariaDB Connector/J 2.7.6 で再現しました。MySQL Connector/J のメッセージは、MySQL のバグ票の記載です(確認日 2026年10月11日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「ResultSet closedの直し方|Operation not allowed」

原因① 同じ Statement をループの中で使い回した

Statement st = con.createStatement();
ResultSet rs = st.executeQuery("SELECT id FROM j_users ORDER BY id");
while (rs.next()) {                                   // ← 2周目でエラー
    ResultSet r2 = st.executeQuery(                   // ← 同じ st で実行
        "SELECT name FROM j_users WHERE id=" + rs.getInt(1));
    r2.next();
    out.add(r2.getString(1));
}
ドライバ 結果
PostgreSQL(pgJDBC 42.7.2) This ResultSet is closed.
MySQL(MariaDB Connector/J 2.7.6) エラーにならず [山田, 佐藤]
MySQL Connector/J Operation not allowed after ResultSet closed(MySQL のバグ票 #620 の例:ループの中で同じ Statement で UPDATE)

内側の st.executeQuery を実行した時点で、外側の rs が閉じられ、次の rs.next() でエラーになります。 公式ドキュメントでも、「既定では Statement 1つにつき、同時に開ける ResultSet は1つ。2つを交互に読むなら、別々の Statement で作る」と説明されています。

MariaDB Connector/J では動きましたが、これはドライバの作りによるもので、JDBC の決まりに沿った書き方ではありません。 ドライバを変えた・版を上げたときに止まる原因になります。

直し方

try (Statement st = con.createStatement();
     ResultSet rs = st.executeQuery("SELECT id FROM j_users ORDER BY id");
     PreparedStatement ps = con.prepareStatement("SELECT name FROM j_users WHERE id = ?")) {
    while (rs.next()) {
        ps.setInt(1, rs.getInt(1));
        try (ResultSet r2 = ps.executeQuery()) {      // ← 別の Statement で実行
            r2.next();
            out.add(r2.getString(1));
        }
    }
}

両方のドライバで [山田, 佐藤] が取れました。 ループの中で1行ずつ SQL を実行する形は、件数が多いと遅くなるため、JOIN で1回にまとめられないかも検討します(N+1 問題)。既存の記事「N+1問題とは?原因と見つけ方・解決方法【フレームワーク別】」で扱っています。

原因② try-with-resources で閉じた ResultSet を返した

static ResultSet find(Connection con) throws SQLException {
    try (Statement st = con.createStatement()) {
        return st.executeQuery("SELECT id, name FROM j_users ORDER BY id");
    }   // ← ここで st が閉じられ、ResultSet も閉じられる
}

ResultSet rs = find(con);
rs.next();                                            // ← エラー
ドライバ 結果
PostgreSQL This ResultSet is closed.
MySQL(MariaDB Connector/J 2.7.6) エラーにならず 山田

「閉じ忘れを防ぐために try-with-resources にしたら、エラーになった」という形です。ResultSet は Statement と接続にぶら下がっているので、Statement を閉じたあとは使えません。

直し方:値を読み終えてから返す

record User(int id, String name) {}

static List<User> find(Connection con) throws SQLException {
    try (Statement st = con.createStatement();
         ResultSet rs = st.executeQuery("SELECT id, name FROM j_users ORDER BY id")) {
        List<User> list = new ArrayList<>();
        while (rs.next()) list.add(new User(rs.getInt("id"), rs.getString("name")));
        return list;                                   // ← 読み終えた値を返す
    }
}

ResultSet をメソッドの外に出さず、中で List などに詰め替えて返します。 閉じ忘れも、閉じたあとの読み込みも起きません。

自分で閉じた・接続を閉じた

書いたコード PostgreSQL MySQL(MariaDB Connector/J)
rs.close() のあと rs.next() This ResultSet is closed. Operation not permit on a closed resultSet
con.close() のあと con.createStatement() This connection has been closed. createStatement() is called on closed connection

確認の手順(チェックリスト)

順番 確認すること 方法
1 同じ Statement で、ループの中で別の SQL を実行していないか st.execute... の変数名
2 ResultSet をメソッドから返していないか 戻り値の型が ResultSet
3 try-with-resources の外で ResultSet を読んでいないか } のあとで rs. を使っていないか
4 ドライバを変えた・版を上げたか 以前は動いていた、は決まりに沿っていたとは限らない
5 1行ずつ SQL を実行していないか JOIN でまとめる

よくある質問

Q. 以前は動いていたのに、急にエラーになりました。 A. 確認環境のように、ドライバによっては決まりに沿わない書き方でも動くことがあります。ドライバの変更や版の更新で止まった場合は、上の2つの形になっていないかを確かめます。MySQL のバグ票 #620 では、Connector/J が 3.0 以降で JDBC の決まりに沿うようになった、という説明があります。

Q. カーソルの位置のエラー(next を呼んでいない)とは違いますか。 A. 違います。関連記事の「ResultSet not positioned properlyの直し方|JDBC」で扱っています。

まとめ

  • 閉じられた ResultSet を読もうとしたエラー。PostgreSQL は This ResultSet is closed.
  • 同じ Statement で別の SQL を実行すると、前の ResultSet は閉じる(JDBC の決まり)
  • Statement を閉じると ResultSet も閉じる。try-with-resources の中から ResultSet を返さない
  • 値を List などに詰め替えて返す
  • MariaDB Connector/J では動いてしまう例があった。動いても直しておく

参考・出典

確認日はいずれも 2026年10月11日です。

  • Java SE 21 API「Statement」:https://docs.oracle.com/en/java/javase/21/docs/api/java.sql/java/sql/Statement.html
  • MySQL Bugs「Bug #620 Operation not allowed after ResultSet closed Exception」:https://bugs.mysql.com/bug.php?id=620

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「ResultSet closedの直し方|Operation not allowed」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?