Javaのスコープとは?ページ・リクエスト・セッション・アプリケーションの違い

検索結果は今回の画面に渡したい。ログイン情報は次の画面でも使いたい。サイト名は誰がアクセスしても同じものを使いたい。このように、データによって「どこまで使うか」が違います。保存先をすべて同じにすると、必要な値を次の画面で取得できなかったり、別の利用者の情報で上書きされたりします。
Servlet・JSPでは、名前を付けてデータを保存する場所と、そのデータを使える範囲を選べます。この記事ではページ・リクエスト・セッション・アプリケーションの4つのスコープを、保存先、共有範囲、保持期間で比べます。
本記事は、Schoo「Java入門 中級」第8回「Webアプリケーションにおける状態管理」のrequestとsessionの学習を土台に、ページスコープとアプリケーションスコープまで広げて整理します。授業を受けていなくても、具体例から読める構成です。
スコープは「誰が、いつまで使うデータか」で選ぶ
- このJSPの表示処理だけで使う見出し → ページスコープ。
- 今回の検索結果をServletからJSPへ渡す → リクエストスコープ。
- 同じ利用者の操作で、画面を移動しても使う表示設定やログイン情報 → セッションスコープ。
- 同じWebアプリの利用者全員で共通に使うサイト名 → アプリケーションスコープ。

| スコープ | 保存先・使える範囲・保持期間 |
|---|---|
| ページ | JSPのpageContextに保存。そのJSPの今回の処理中だけ使う。同じJSPでも次のアクセスでは新しい範囲になる。 |
| リクエスト | requestに保存。同じリクエストの処理中に使う。ServletからJSPへforwardしても使えるが、新しいリクエストには自動で引き継がれない。 |
| セッション | HttpSessionに保存。同じセッションの複数リクエストで使う。タイムアウトやinvalidateによる無効化まで保持できる。 |
| アプリケーション | ServletContextに保存。同じWebアプリの処理で共有する。アプリの終了・再配置などで、その保存先の寿命が終わる。 |
いずれも、途中でremoveAttributeを呼べばその名前の属性を削除できます。表の期間ずっと必ず値がある、という意味ではありません。また、アプリケーションスコープはデータベースのような永続保存先ではありません。
「変数のスコープ」ではなく「名前付きのデータの保存範囲」
Javaのメソッド内で宣言するローカル変数のスコープと、ここで扱うスコープは分けて考えます。ここでは、requestなどのオブジェクトに、setAttribute("名前", 値)で属性を保存し、getAttribute("名前")で取り出します。
request.setAttribute("resultCount", Integer.valueOf(3));
この例なら、保存先はrequest、名前はresultCount、値はInteger型の3です。取り出すときも、同じ保存先と同じ名前を指定します。名前は大文字・小文字も区別されます。getAttributeはObject型で返すので、保存した型に合わせてキャストします。
フォームやURLから届くgetParameterの値とは別です。リクエストパラメータはブラウザから届く文字列、属性はサーバー側のプログラムが保存したデータです。DTOやListなどのオブジェクトも属性として扱えます。
以降は、既存のServletのメソッド内やJSPに置く説明用の抜粋です。Jakarta ServletのWebアプリを前提とし、DB接続や授業ファイルは使いません。固定の文字列と数値で、保存先の違いに絞って確認します。
1.ページスコープ:そのJSPの今回の処理で使う
list.jspの表示部分
<%
pageContext.setAttribute("heading", "商品一覧");
String heading = (String) pageContext.getAttribute("heading");
%>
<h1><%= heading %></h1>
このJSPでは「商品一覧」と表示します。pageContext.setAttributeで範囲を省略すると、ページスコープへ保存します。pageContext.getAttribute("heading")も、この書き方ではページスコープだけを探します。
ページスコープは、ブラウザのタブに保存する機能ではありません。サーバーが今回のJSPを処理している間の保存範囲です。別のJSPへforwardしても、headingはそのJSPのページスコープへ引き継がれません。
同じJSPを再読み込みすれば、このコードが再実行されるので「商品一覧」と表示されます。前回の値が残っていたわけではありません。上のString型のローカル変数headingと、pageContextに保存した属性headingも、別のものです。
この短い表示だけならローカル変数でも書けます。ここではページスコープの保存・取得を示すために属性を使っています。Servletの検索結果をJSPへ渡したい場合は、次のリクエストスコープを使います。
2.リクエストスコープ:今回の結果をServletからJSPへ渡す
検索を担当するServletのdoGet内
// 検索結果が3件だった場合の例
request.setAttribute("resultCount", Integer.valueOf(3));
request.getRequestDispatcher("/result.jsp")
.forward(request, response);
result.jspの表示部分
<%
Integer resultCount = (Integer) request.getAttribute("resultCount");
%>
<% if (resultCount != null) { %>
<p>検索結果:<%= resultCount %>件</p>
<% } else { %>
<p>検索結果が渡されていません。</p>
<% } %>
Servletからforwardして表示すると、JSPには「検索結果:3件」と表示されます。ServletとJSPは別のファイルでも、同じリクエストを処理しているため、保存したresultCountを取り出せます。result.jspはWeb公開フォルダーの直下に置く想定です。
一方、アドレス欄にresult.jspのURLを直接入力すると、新しいリクエストになるためresultCountはありません。このコードでは「検索結果が渡されていません。」と表示します。nullは「0件」ではなく、ここではその名前のデータが渡されていないことを表します。
一覧の検索結果や今回の入力エラーなど、今回の画面表示に必要なデータに向いています。ページを開き直して同じ結果を見せたいなら、Servletが新しいリクエストで検索し直し、改めて属性に保存してJSPへ渡します。
3.セッションスコープ:同じ利用者の操作をまたいで使う
商品一覧の表示を「リスト形式」に変更し、次の画面でもその設定を使いたい場合を考えます。表示方法を保存するServletの処理では、次のようにします。
表示方法を保存するServletの処理内
HttpSession session = request.getSession();
session.setAttribute("displayMode", "list");
HttpSessionはjakarta.servlet.http.HttpSessionをimportして使います。次の画面を担当するServletでは、既存のセッションから設定を読み取ります。
次の画面を担当するServletの処理内
HttpSession session = request.getSession(false);
String displayMode = null;
if (session != null) {
displayMode = (String) session.getAttribute("displayMode");
}
boolean listView = "list".equals(displayMode);
同じセッションが有効ならdisplayModeはlistとなり、listViewはtrueです。getSession(false)は、セッションがなければ新しく作らずnullを返します。この例では、セッションや設定がない場合はlistViewがfalseになります。実際の画面では、未設定時の標準表示も決めて使います。
新しいリクエストでも値を使えるのは、ブラウザがCookieのセッションIDを送り、サーバーが同じセッションを識別できるからです。表示設定やログイン情報そのものをCookieに入れているわけではありません。
ログイン情報も、本人確認に成功した後でセッションへ保存する代表例です。ただし、セッションは利用者アカウントやタブと必ず1対1になるものではありません。同じブラウザの別タブでは通常Cookieを共有するため、一方で設定したdisplayModeをもう一方でも使う場合があります。
設定だけ消すならsession.removeAttribute("displayMode")、セッション全体を無効にするならsession.invalidate()です。ログアウトやタイムアウト後も永久に残る保存場所としては扱いません。
4.アプリケーションスコープ:同じWebアプリで共有する
全員に同じ「学習ストア」というサイト名を表示する場合は、ServletContextの属性として共有できます。Servlet側ではgetServletContext()、JSP側ではapplicationという名前で、同じWebアプリのServletContextを使えます。
初期化を担当するServletのinitメソッド
@Override
public void init() {
getServletContext().setAttribute("siteName", "学習ストア");
}
initはそのServletの初期化時に実行されるメソッドです。この例はこの初期化が済んだ後にJSPを表示する前提です。起動時にそのServletを初期化する設定には、たとえば@WebServlet(value = "/settings", loadOnStartup = 1)があります。通常の画面表示のたびにサイト名を書き込む必要はありません。
JSPで共通のサイト名を表示する部分
<%
String siteName = (String) application.getAttribute("siteName");
%>
<p><%= siteName %></p>
ブラウザAから開いても、別のセッションを使うブラウザBから開いても、同じServletContextに保存された「学習ストア」を読み取れます。ログイン中の利用者IDのように、人によって異なる情報をここへ保存してはいけません。
| 利用者ごとの情報を共通の場所に置くと | 起きること |
|---|---|
| Aさんのログイン成功 | 共通のloginUserIdに1001を保存する。 |
| Bさんのログイン成功 | 同じ名前に2002を保存し、1001を上書きしてしまう。 |
| Aさんが次の画面を開く | 共通の保存先から読むと2002になり、別の利用者として扱う危険がある。 |
アプリケーションスコープの「全員」とは、同じWebアプリの同じServletContextを使う処理のことです。サーバー上の別のWebアプリまで自動で共有するものではなく、複数サーバーの値が自動で同期されるものでもありません。
また、複数のリクエストが同時に共有データを操作できます。共有するListや件数を無造作に変更すると競合するため、更新には別途対策が必要です。最初はこの例のような初期化後に読み取る共通情報と、利用者ごとの情報を分けて考えましょう。
フォワードとリダイレクトで、requestの値はどうなる?

先ほどのforwardの代わりに、Servletで次の処理をした場合を考えます。
result.jspへリダイレクトするServletのdoGet内
request.setAttribute("resultCount", Integer.valueOf(3));
response.sendRedirect(request.getContextPath() + "/result.jsp");
return;
ブラウザがresult.jspへアクセスし直すと、resultCountはnullになります。同じサーバー内のURLでも、新しいリクエストだからです。先ほどのJSPなら「検索結果が渡されていません。」と表示します。
| 画面の移り方 | 保存した属性の扱い |
|---|---|
| ServletからJSPへforward | 同じrequestの属性を使える。移動先JSPのページスコープは別。 |
| リンクのクリック・再読み込み・redirect後のアクセス | 新しいrequestになる。以前のrequest属性は自動で引き継がれない。 |
| 同じセッションでの次のアクセス | セッションが有効で識別できれば、session属性を引き続き使える。 |
| 別のセッションからのアクセス | session属性は別。application属性は同じWebアプリ内で共有する。 |
requestの値を失いたくないからといって、何でもsessionへ移すのは避けます。更新後にredirectして最新の一覧を表示するなら、移動先のServletで検索し直してrequestへ保存する方法があります。セッションには、操作をまたいで保持する理由のあるデータを置きます。
同じ名前でも、保存先が違えば別の属性
request.setAttribute("message", "今回の画面のメッセージ");
session.setAttribute("message", "次の画面でも使うメッセージ");
上のsessionは、あらかじめ取得した有効なHttpSessionとします。どちらもmessageという名前ですが、request.getAttribute("message")はrequestから、session.getAttribute("message")はsessionから取り出します。片方に保存しても、もう片方へ自動コピーされるわけではありません。
JSPのpageContext.getAttribute("message")も、引数が名前だけならページスコープの属性を取り出します。requestやsessionまで順番に探すメソッドではありません。学習中は、何をどこへ保存したかがわかる名前と取得先にそろえると、取り違えを防げます。
迷ったときは、必要以上に広い場所へ保存しない
| 保存したいデータ | 選び方 |
|---|---|
| 今回のJSPだけで使う表示用の属性 | ページ。単なる途中計算ならローカル変数で足りることもある。 |
| 検索結果・今回の入力エラー | リクエスト。今回の結果をJSPへ渡す。 |
| 画面を移動しても必要なログイン情報・表示設定 | セッション。ただし利用者アカウントやタブそのものではなく、セッション単位。 |
| 全員に共通のサイト名など | アプリケーション。利用者固有の情報を混ぜない。 |
| 再起動後も残す注文・会員・設定など | DBなどへの永続保存を検討。4つのスコープだけに頼らない。 |
たとえば入力エラーをsessionへ保存したまま削除し忘れると、次の操作でも古いエラーが出ることがあります。検索結果をsessionへ入れると、別タブの検索で上書きされる場合もあります。保持期間が長いほど便利、ではありません。必要な共有範囲と期間に絞ります。
確認問題
- 商品一覧のJSPだけで使う見出し、今回の検索結果、画面を移動しても使う表示設定、全員共通のサイト名を保存します。属性として扱うなら、それぞれどのスコープが合いますか。
- requestにresultCountとして3を保存しました。同じresult.jspへforwardする場合とredirectする場合で、記事のJSPにはそれぞれ何が表示されますか。
- Servletでrequest.setAttribute(“heading”, “商品一覧”)を実行してJSPへforwardしました。JSPのpageContext.getAttribute(“heading”)では取得できませんでした。なぜですか。どう直しますか。
- AさんとBさんのログイン情報をgetServletContext().setAttribute(“loginUserId”, 値)で保存しています。Bさんのログイン後、何が起きる危険がありますか。保存先はどう分けますか。
- 同じセッションを共有する別タブで、一方からdisplayModeを変更しました。もう一方には影響しますか。また、session.removeAttribute(“displayMode”)とsession.invalidate()はどう違いますか。
演習後の確認:こちらのページを使って、自分で考えた内容を見直せます。
まとめ
- ページは今回のJSP内、requestは今回のリクエスト内で使う。
- sessionは同じセッションの複数リクエストで使い、applicationは同じWebアプリで共有する。
- forwardではrequest属性を渡せるが、redirect後は新しいrequestになる。
- 保存先と属性名の両方を合わせて取り出す。
- 利用者固有の情報を全員共通の保存先へ置かず、必要な範囲と期間を選ぶ。