Spring」タグアーカイブ

Java Spring/SpringBoot習得向けおすすめ本

Java新人エンジニア必読!Spring/SpringBoot習得向けおすすめ参考書ロードマップ

Javaの市場需要と若手エンジニアのキャリア戦略

Javaはプログラミング言語の中で最も長い歴史を持ち、2026年現在でも企業システムの中核を担っています。Python、GoLang、Rust といった新言語が台頭する中でも、既存のエンタープライズシステムの運用・保守・改善業務において、Javaのスキルは今後も高い市場価値を維持されると予想されます。

特に若手エンジニアにとって、Java と Spring/Spring Boot の習得は以下のメリットがあります:

  • 求人数が多い:大手IT企業、金融機関、製造業など、あらゆる業界で Javaエンジニアの需要は依然として高い
  • 年収が高い傾向:エンタープライズシステム開発の経験は給与交渉時に有利に働く
  • キャリアパスが豊富:バックエンド開発、クラウドアーキテクチャ、マイクロサービス設計など、幅広い領域へ展開可能
  • モダンな開発手法の学習:Spring Boot は最新のクラウドネイティブ開発、コンテナ化(Docker・Kubernetes)と親和性が高く、次世代技術へのステップとなる

Java21・Spring 6時代のトレンドと参考書の重要性

2023年9月にリリースされた Java 21 は LTS(Long-Term Support)バージョンであり、以下のような革新的な機能が導入されました:

  • 仮想スレッド(Virtual Threads):スケーラビリティの大幅向上
  • レコードと switch 式の拡張:簡潔で読みやすいコード記述
  • テキストブロック(Text Blocks):SQL、JSON の記述がシンプルに

Spring 6 は Java 17 以上をサポートし、GraalVM ネイティブコンパイル対応、AOT(Ahead-of-Time)コンパイル対応など、次世代クラウド環境への適応が加速しています。

しかし、こうした最新トレンドの一方で、基礎を理解することは依然として不可欠です。本ロードマップで紹介する参考書は、モダンな Javaエコシステムの土台となる知識を、体系的かつ実践的に習得するために厳選されたものです。

このロードマップが対象とする読者

このロードマップは以下のような若手エンジニアを想定しています:

  • 新卒・既卒で IT企業に就職し、いきなり「Javaを学べ」と言われている人
  • Python や JavaScript の経験はあるが、Java は初めての人
  • Spring / Spring Boot を使ったシステム開発に携わることになり、体系的に学びたい人
  • エンタープライズシステムの開発現場で、プロフェッショナルとして活躍したい人

参考書を通じた段階的スキル習得の方針

単なる言語機能の習得ではなく、以下の3つのフェーズを経て、実務で即戦力となるレベルを目指します:

  1. 基礎フェーズ:Java の基本構文、オブジェクト指向の理解
  2. 実践フェーズ:デザインパターン、コード品質、Web 技術の理解
  3. 応用フェーズ:Spring / Spring Boot、設計思想(DDD)の習得

本記事では、10年以上のエンタープライズシステム開発経験を持つ筆者が、この3つのフェーズを通じて習得すべき良質な参考書を厳選し、各書籍の学習ポイントを詳しく解説します。

本文の構成

本文では、結果で紹介する書籍を1冊ずつ詳しく解説しています。構成は以下の通りです:

Springの3冊を最初に学習

まずプロになるためのSpring入門Spring徹底入門Spring Boot 3 プログラミング入門の3冊を紹介します。JavaでSpringを本格的に使うために必要な知識が詰まっており、DIコンテナの理解やフレームワークの使い方を学べます。

Java基礎から応用までの8冊

その後、やさしいJava からスタートして、入門書2冊で基礎を固め、プロになるJavaでレベルアップ。デザインパターンリーダブルコードオブジェクト指向など、実務で必要な知識を段階的に学んでいきます。最後にWebを支える技術で、Webシステム開発に必要な基礎知識を習得します。

番外編:ドメイン駆動設計の3冊

最後に、良い設計を目指す人向けに、ドメイン駆動設計入門から始まり、エリック・エヴァンスのドメイン駆動設計実践ドメイン駆動設計へとステップアップする3冊を紹介します。

想定読者

  • 仕事を始めていきなり「とりあえずJava出来るようになって」と言われた人
  • JavaをSpringまで理解したい人

結果

いきなり結果です。
忙しい人はとりあえず結果だけご覧ください。

Spring良書

  1. プロになるためのSpring入門
  2. Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発
  3. Spring Boot 3 プログラミング入門

Java良書8選

  1. やさしいJava
  2. スッキリわかるJava入門
  3. スッキリわかるJava入門 実践編
  4. プロになるJava
  5. Java言語で学ぶデザインパターン入門
  6. リーダブルコード
  7. オブジェクト指向でなぜつくるのか
  8. Webを支える技術 ―― HTTP,URI,HTML,そしてREST

番外編:ドメイン駆動設計良書3選

  1. ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本
  2. エリック・エヴァンスのドメイン駆動設計
  3. 実践ドメイン駆動設計

それではここから1冊ずつ紹介です。

プロになるためのSpring入門

最初はプロになるためのSpring入門です。

プロになるためのSpring入門

Springを学ぶ最初の一冊がこれです。
Springを理解する上で最も重要なDIコンテナについて、実装はもちろん概念/理解する上でのイメージ図を含めて詳しく書かれているところが、プロを名乗るだけのことはある良本だと思えました。
JavaからSpringにそのまま学習を進めるのに最適な一冊で、同等の知識レベルで書かれています。

Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発

次はSpring徹底入門 Spring FrameworkによるJavaアプリケーション開発です。

Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発

数あるフレームワークの中でも、Javaで本格的なシステム開発をする際に必ず言っていいほど出てくるフレームワーク(その後、ほぼ採用されるフレームワーク)がSpring Frameworkです。具体的なシェアは把握していませんが、私が経験したプロジェクトではほぼ使用されていました。そのSpring Frameworkを初めて学ぶ方におすすめの入門書がこの一冊です。
この本は、初心者から中級者までを対象に、Spring Frameworkの基本から応用までをわかりやすく解説しています。多くの開発者に支持され、Springを学ぶ際に手に取るべき書籍とされています。その人気の証として、2024年5月には待望の第2版が発売されました。
最新の情報とともにさらに充実した内容で、Spring Frameworkの理解を深め、読者のスキルアップに貢献する一冊となると思います。
今やSpring Frameworkを扱う場合、必ず購入する本になっているのではないでしょうか。

Spring Boot 3 プログラミング入門

いよいよJava10冊のラストはSpring Boot 3 プログラミング入門です。

Spring Boot 3 プログラミング入門

クラウド開発がデファクトスタンダードになっている今では、SpringからSpring Bootという選択肢が増えていると思います。
Spring Bootに特化している参考書がまだまだ少ないのでこの本だけでは足りない部分もありますが、Spring Bootの入門書としての位置付けではこの本がマストと考えています。
人気書籍だったSpring Boot 2プログラミング入門の改訂版で、2023年2月27日発売のJDK17、Spring 6対応です。

やさしいJava

ここからはSpringを学ぶ前にJavaを基礎から学びたい人のための本の紹介です。最初はやさしいJavaです。

やさしいJava

Javaの入門書は今となっては多すぎるほどあるのですが、まずは入門書としてタイトルの通り非常にわかりやすいこの本から入るのがいいかなと思っています。
Javaの特徴として、プログラムの理解もありますが、他の言語に比べわかりにくい専門用語多く、初心者には最初のハードルがまず高い事が挙げられると思っています。
そのあたりをうまく説明して導入時の挫折が避けられるのが、この1冊からスタートすることかなと思って選出しています.

スッキリわかるJava入門

次はスッキリわかるJava入門です。

スッキリわかるJava入門

私の経験からですが、プログラミングの入門書は1冊では足りないと思っています。
理由としては、入門書だけで書ききれない重要なことが多く1冊では網羅できないこと。専門用語の解説がわかりやすい表記ではあるのですが、自分の中ですっきり理解できるかはその人の今までの経験によるところがあるからです。
そのため、入門書の位置付けで2冊目の紹介です。
2冊を読み比べて本当に重要なところ、わかりにくい概念の表現方法を比較して自分の理解力を高めることが重要と考えています。
人気シリーズのため最近実践編とともに第4版が発売されました。

スッキリわかるJava入門 実践編

次はスッキリわかるJava入門 実践編です。
タイトルの通りですが、スッキリわかるJava入門の続編です。

スッキリわかるJava入門 実践編

やさしいJavaと入門編でじっくり基礎を固めた後に実践編に入る流れで読み進めるといいかなと思います。

プロになるJava

次はプロになるJavaです。
タイトルの通りですが、ここからプロになりましょう(^^)

プロになるJava

ここまでで基礎を固める -> 実践を学ぶ -> プロになる というレベルアップを想定しています。
印象としては、実践を学ぶ -> プロになる 部分はちょっと飛躍的レベルアップなのですが、その部分は実際に自分の手を動かしてプログラミングした結果や実際やったこと読んだことをGoogle検索等で補完しながらレベルアップすることを想定しています。(ここまで読んだ読者ならそれが出来るだけのレベルアップがされていると考えています)

この本は、ITエンジニア本大賞2023 技術書部門ベスト10にも選ばれました。(Java関連の本で2023年のベスト10に選ばられるのは非常に凄い事だと思っています)これでJavaを仕事にする人は、必ず読んだ方が良い本になったのではないでしょうか。

Java言語で学ぶデザインパターン入門

プロになったら次はデザインパターンを学びましょう。

Java言語で学ぶデザインパターン入門

正直プロなったところがJavaで給料をもらえるというスタートラインと思います。
つまり、ここまでのJavaプログラマーでは現場で技術力の差別化が出来ません。
そこで、オッこの人はJavaがかなり出来るなと思ってもらえるようにデザインパターンを学びましょう。
現場でこれはGoFのAdapterのデザインパターンですね。なんて、会話が出来るとJavaで自信をもって仕事をしているなと言えると思います。
本書は、2001年に発売されて以来、増補改訂され2021年に発売されたものです。
実際のコードが記載されているのはもちろんのこと、デザインパターンの概念や用途を直感的に理解できるように書かれています。また、デザインパターンの使い方だけでなく、パターンを使わない場合の問題点や欠点、パフォーマンスについても考慮された説明がなされているため、より実践的な学びを得ることができます。
Javaのスペシャリストとして学んでおくべき1冊になっています。

リーダブルコード

次はリーダブルコードです。
Javaを十分学んできたので、ちょっと箸休めです。

リーダブルコード

内容を知っている人は、なぜここでリーダブルコード?と思う人もいると思うのですが、私はこのタイミングがベストと思います。
プログラム初心者が読む本として位置付けされることもあると思いますが、私はJavaが分かってから読むべき(一度読んでいても再読すべき)と考えています。
自身でJavaコードをある程度書けるようになってからこの本を読むとよりリアルな気づきがあると思います。そう言えばあの時書いたあのコード、リーダブルではなかったな。など。
コメントの書き方、関数やメソッドの命名方法、制御フローのシンプル化、適切なフォーマットなどが書かれており、これらのテクニックやスタイルを使うことで、コードの読みやすさが向上し、保守性や拡張性が高まるコードが書けます。
仕事でJavaを書く場合1人で完結しないことが多いです。何人かのチームでJavaを書くときに読みやすいコードを書くことはチームでの開発において非常に重要です。チームで開発を行う場合、コードの読みやすさはコミュニケーションコストを減らし、コードの品質を向上させるために不可欠です。ぜひ1度読んで、プロフェッショナルとして読みやすいコードが書けるように意識してみてください。
ちなみにこの本は少し古いですが、ITエンジニア本大賞2014 技術書部門大賞の本です。

オブジェクト指向でなぜつくるのか

次はオブジェクト指向でなぜつくるのかです。
こちらもJavaに特化した内容ではないため箸休め第2弾です。

オブジェクト指向でなぜつくるのか

そもそもなぜJavaがここまで流行ったのか、オブジェクト指向はどんなに便利なのか、Javaでオブジェクト指向で作るというのはどういうことなのか理解するのに良い本と考えています。

Webを支える技術 ―― HTTP,URI,HTML,そしてREST

次はWebを支える技術 ―― HTTP,URI,HTML,そしてRESTです。
箸休め第3弾(最後の箸休め)です。そろそろプログラムを見ることに疲れた人向けです(^^;)

Webを支える技術 ―― HTTP,URI,HTML,そしてREST

皆さんがJavaを勉強する理由は様々だと思いますが、最近はWebシステムを作るケースが多いのではないでしょうか。
そんな人向けの本になっているので、私はWeb関係ないという人は飛ばしてもらっていいのですが、Javaで「HTTP,URI,HTML,そしてREST」をプログラミングするという人はぜひ読んでおいてください。
コードが書けるだけではWebシステムは完成しません。なぜそのようなコードが必要かを理解できるようになると思います。

番外編:ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本

ここからは番外編になります。
まずはドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本です。
良いプログラムは良い設計の上に成り立ちます。
良いプログラムでも設計がいまいちであればそのシステムは扱いにくいものになるでしょう。

では、良い設計とは何か?
私の中での1つの答えはドメイン駆動設計と思っています。
ここからは設計というさらに上を目指す意識高い系の皆さん向けの番外編になります。

ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本

まずはドメイン駆動設計の入門書という位置づけのこの本を選びました。
最終的なゴールは最後に紹介する実践ドメイン駆動設計になるのですが、実践ドメイン駆動設計をいきなり読んで挫折なく読み切れる人はなかなかいないでしょう。(私も一度挫折しました。。。)
そのため、まずはこの本を入門書として位置付けています。

番外編:エリック・エヴァンスのドメイン駆動設計

次はエリック・エヴァンスのドメイン駆動設計です。

エリック・エヴァンスのドメイン駆動設計

一応、この本を間に挟む順番としました。
この本がなくても実践ドメイン駆動設計を理解できそうという人は直接実践ドメイン駆動設計を読んでもいいのですが、この本を挟んだ方が理解しやすいだろうなぁと思いラインナップに入れています。

番外編:実践ドメイン駆動設計

いよいよ最後実践ドメイン駆動設計です。

実践ドメイン駆動設計

この本の紹介はもはや何もいらないでしょう。
というぐらいドメイン駆動設計と言えば、この本という本になっています。
ただ、前述した通りいきなりこの本を読んで理解できる人は少ないと思うので今回は3冊を紹介する形で記載しました。

まとめ

今回は、Java初心者がSpring/Springbootを使えるようになるまでの10冊とドメイン駆動設計の3冊を紹介しました。
これらを理解すれば間違なく私はJavaのプロフェッショナルです。と、自信をもって仕事ができる人材になれるかなと思って選抜しているので、これからJavaを頑張ろうと思っている人がいたらぜひ参考に1冊でも読んでみてください。

おまけ

Javaの開発環境と言えばEclipseです。
最近はVSCodeも人気になっているのでEclipseのオススメ本が少なくなってきた印象です。
ただこの本は、この1冊読んでおけば困らない。という内容になっているのでまさにパーフェクトガイドだと思います。


JavaエンジニアのためのEclipse パーフェクトガイド

本投稿が少しでも皆さんの良いJavaライフの助けになればと思います( )

JavaのSpringSecurityでAESの暗号化/復号化処理を実装する

サクっと作るなら以下が参考になります。

http://terasolunaorg.github.io/guideline/current/ja/Security/Encryption.html

暗号化

    public static String encryptTextByAesWithGcm(String secret, String salt, String plainText) {
        TextEncryptor aesTextEncryptor = Encryptors.delux(secret, salt);

        return aesTextEncryptor.encrypt(plainText);
    }

復号化

    public static String decryptTextByAesWithGcm(String secret, String salt, String cipherText) {
        TextEncryptor aesTextEncryptor = Encryptors.delux(secret, salt);

        return aesTextEncryptor.decrypt(cipherText);
    }

補足

ここからが個人的には重要

1.secretはプレーンテキストでいいのですが、saltはプレーンテキストではダメなので以下みたいになります。

String salt = new String(Hex.encodeHexString("salt".getBytes()));

2.参考サイト

暗号化の結果について
encryptメソッドの返り値 (暗号化の結果) は実行毎に異なる値を返すが、鍵とソルトが同一であれば復号処理の結果は同一になる (正しく復号できる) 。

の記載、初期化ベクトル (iv)の役割よるものです。
ivは、同じ鍵を使用して同じ平文データを複数回暗号化するときに異なる暗号文を生成します。
ivはランダムな値で、セッションごとに生成される。

セッションごと?確かにソースをSpringSecurityのソースを追っていくと、以下になっている。

    /**
     * Creates a standard password-based bytes encryptor using 256 bit AES encryption.
     * Derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation
     * Function #2). Salts the password to prevent dictionary attacks against the key. The
     * provided salt is expected to be hex-encoded; it should be random and at least 8
     * bytes in length. Also applies a random 16-byte initialization vector to ensure each
     * encrypted message will be unique. Requires Java 6. NOTE: This mode is not
     * <a href="https://en.wikipedia.org/wiki/Authenticated_encryption">authenticated</a>
     * and does not provide any guarantees about the authenticity of the data. For a more
     * secure alternative, users should prefer
     * {@link #stronger(CharSequence, CharSequence)}.
     * @param password the password used to generate the encryptor's secret key; should
     * not be shared
     * @param salt a hex-encoded, random, site-global salt value to use to generate the
     * key
     *
     * @see Encryptors#stronger(CharSequence, CharSequence)
     */
    public static BytesEncryptor standard(CharSequence password, CharSequence salt) {
        return new AesBytesEncryptor(password.toString(), salt, KeyGenerators.secureRandom(16));
    }

以下のJavaDocによると、、、

https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/crypto/encrypt/AesBytesEncryptor.html

new AesBytesEncryptorの第三引数の KeyGenerators.secureRandom(16) がiv

確かに乱数

では、復号化にも同じ値のivがいるのかなと思ったらソース見たら以下のように暗号化後のテキストから取得しているようなので大丈夫そうでした。

    @Override
    public byte[] decrypt(byte[] encryptedBytes) {
        synchronized (this.decryptor) {
            byte[] iv = iv(encryptedBytes);
            CipherUtils.initCipher(this.decryptor, Cipher.DECRYPT_MODE, this.secretKey, this.alg.getParameterSpec(iv));
            return CipherUtils.doFinal(this.decryptor,
                    (this.ivGenerator != NULL_IV_GENERATOR) ? encrypted(encryptedBytes, iv.length) : encryptedBytes);
        }
    }

    private byte[] iv(byte[] encrypted) {
        return (this.ivGenerator != NULL_IV_GENERATOR)
                ? EncodingUtils.subArray(encrypted, 0, this.ivGenerator.getKeyLength())
                : NULL_IV_GENERATOR.generateKey();
    }

まとめ

ということで改めて復習です。

Secret:
Secretは一般的にユーザーが選択する文字列で、秘密情報を保護するために使用されます。
Secretベースのセキュリティでは、ユーザーのSecretから導かれた鍵を生成します。このとき、パスワードを安全に保管し、強力な鍵導出関数(PBKDF2、bcrypt、Scryptなど)を使用することが重要です。

Salt:
SaltはSecretベースのセキュリティで使用され、Secretに追加されるランダムなデータです。
Saltはユーザーごとに異なり、同じSecretを持つユーザーのハッシュ値を異なるものにします。これにより、レインボーテーブル攻撃から保護されます。

初期化ベクトル (iv):
ivは、同じ鍵を使用して同じ平文データを複数回暗号化するときに異なる暗号文を生成します。
ivはランダムな値で、セッションごとに生成される。

SpringMVCを使ったJavaプログラムにおけるシングルトンとスタティック変数・メソッドの効果を検証してみた

マルチスレッドでのスタティック変数・メソッドの効果を検証してみた

JavaのSpringMVCを使用して、シングルトンパターンとスタティック変数、スタティックメソッドを実装してみました。これらはJavaアプリケーションでよく使用される概念なんですが、ちょっと混乱してきたので、自分なりの整理結果になります。詳細は以下をご覧ください。

検証ソース

  • コントローラソース
@Controller
@RequestMapping("/hello")
public class HelloWorldController {
    private int counter = 0;

    @RequestMapping("/thread")
    public ModelAndView thread(@RequestParam("id") String id) {
        System.out.println("Method: " + new Object(){}.getClass().getEnclosingMethod().getName());
        System.out.println("Parameter:" + id);

        counter = CommonUtil.countup(counter, id);

        CommonUtil com = new CommonUtil();
        counter = com.countdown(counter, id);

        String NewTime1 = CommonUtil.newtime(id);
        String NewTime2 = CommonUtil.newtime(id);

        System.out.println("NewTime1:" + NewTime1);
        System.out.println("NewTime2:" + NewTime2);
        ModelAndView modelAndView = new ModelAndView("thread");
        modelAndView.addObject("id", id);
        modelAndView.addObject("NewTime1", NewTime1);
        modelAndView.addObject("NewTime2", NewTime2);
        return modelAndView;
    }
}
  • 共通処理クラス
public class CommonUtil {

    public static int countup(int var, String id) {
        System.out.println("Method: " + new Object(){}.getClass().getEnclosingMethod().getName());
        var++;
        System.out.println(var);
        return(var);
    }

    public int countdown(int var, String id) {
        System.out.println("Method: " + new Object(){}.getClass().getEnclosingMethod().getName());
        var--;
        System.out.println(var);
        return(var);
    }

    public static String newtime(String id) {
        System.out.println("Method: " + new Object(){}.getClass().getEnclosingMethod().getName());
        // 現在日時を取得
        LocalDateTime now = LocalDateTime.now();
        // フォーマットを指定して文字列に変換
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        String nowStr = now.format(formatter);      
        System.out.println(nowStr);
        return(nowStr);
    }
}
  • 画面表示用HTML
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Thread</title>
</head>
<body>
  <h2>title</h2>
  <p th:text="${id}"></p>
  <p th:text="${NewTime1}"></p>
  <p th:text="${NewTime2}"></p>
</body>
</html>

ソースコードは単純だと思います。
キーはコントローラクラスの変数「private int counter = 0;」
これは SpringのDIコンテナで管理されている Controller のデフォルトのスコープだとシングルトンのためスレッドセーフではありません
では、それを検証していきましょう。

アクセスURL

/hello/thread?id=001
/hello/thread?id=002

マルチスレッドでの検証となるため、URLは二つ用意します。
ログが分かりやすいようにパラメータに id を付与します。
id 自体には処理結果に全く関係ありません。
では、結果です。

結果

順次処理の実行結果

まずは別々にアクセスが来た場合
カウントアップされ直後にカウントダウンされ 1 -> 0、1 -> 0 を繰り返す結果となります。

Method: thread
Parameter:001
Method: countup
1
Method: countdown
0
Method: newtime
2023/04/30 10:04:50
Method: newtime
2023/04/30 10:04:50
NewTime1:2023/04/30 10:04:50
NewTime2:2023/04/30 10:04:50
Method: thread
Parameter:002
Method: countup
1
Method: countdown
0
Method: newtime
2023/04/30 10:04:52
Method: newtime
2023/04/30 10:04:52
NewTime1:2023/04/30 10:04:52
NewTime2:2023/04/30 10:04:52

同時実行の実行結果

では、次に同時実行のケースです。
同時にリクエストが来た場合、以下の結果をなることがあります。
一時的に「counter = 2」になっていることが分かると思います。
要件として「counter」に何をカウントしたいかによりますが、このケースは予期せず起こったケースとなることが多いのではないでしょうか。
もし別々のリクエストでも同じカウントでトータルをカウントしたいケースを想定した場合、「private static int counter = 0;」と明示的にstatic変数にすべきと思います。

Method: thread
Parameter:001
Method: countup
1
Method: thread
Parameter:002
Method: countup
2
Method: countdown
1
Method: newtime
2023/04/30 10:14:05
Method: newtime
2023/04/30 10:14:05
NewTime1:2023/04/30 10:14:05
NewTime2:2023/04/30 10:14:05
Method: countdown
0
Method: newtime
2023/04/30 10:14:13
Method: newtime
2023/04/30 10:14:13
NewTime1:2023/04/30 10:14:13
NewTime2:2023/04/30 10:14:13

ちなみにこの動作確認のやり方としては、1つ目のリクエスト処理中に2つ目のリクエスト処理を先に処理させています。
具体的にはEclipseのデバック機能を使って以下のブレイクポイントを設定しておき、1つ目のリクエスト処理を止めておき、最初に2つ目のリクエスト処理を進めています。

今回は、変数「private int counter = 0;」がキーであるため、
CommonUtilのメソッド「public static int countup(int var, String id)」と「public int countdown(int var, String id)」は
敢えて、countupメソッドの方をstaticメソッドとしていますが、
いずれのケースでも変数「private int counter = 0;」をスタティック変数として使用している結果がわかると思います。
(もし、countdownメソッドの方がスタティック変数を使っていないければ、結果が0にならないはずなので)

補足結果

「public static String newtime(String id)」メソッドでは、変数「private int counter = 0;」を使用していません。
変わりに内部で作成した「String nowStr」を使用しています。
この変数はstaticメソッドのnewtimeメソッド内で作成されていますが、スレッドセーフな変数になります。

public static String newtime(String id) {
        System.out.println("Method: " + new Object(){}.getClass().getEnclosingMethod().getName());
        // 現在日時を取得
        LocalDateTime now = LocalDateTime.now();
        // フォーマットを指定して文字列に変換
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        String nowStr = now.format(formatter);      
        System.out.println(nowStr);
        return(nowStr);
    }

同時実行の実行結果

今回も同時実行して1つ目のリクエスト処理中に2つ目のリクエスト処理を先に処理させています。
ただし、今回は時間がそれぞれ正しく(上書きされずに)取得されて出力されていることがわかると思います。

Method: thread
Parameter:001
Method: countup
1
Method: countdown
0
Method: newtime
Method: thread
Parameter:002
Method: countup
1
Method: countdown
0
Method: newtime
2023/04/30 10:46:22
Method: newtime
2023/04/30 10:46:42
NewTime1:2023/04/30 10:46:22
NewTime2:2023/04/30 10:46:42
2023/04/30 10:46:03
Method: newtime
2023/04/30 10:47:12
NewTime1:2023/04/30 10:46:03
NewTime2:2023/04/30 10:47:12

今回のブレイクポイントは以下に設定しています。

ちなみに画面の表示結果は以下のようになります。

まとめ

最初に書いた通り、キーは変数「private int counter = 0;」です。
これはぱっと見ると、メンバ変数であり、スレッドセーフのように見えますが、SpringのDIコンテナで管理されているインスタントのデフォルトのスコープだと
シングルトン
のためスレッドセーフではありません。
これを常に意識して使いましょう。
補足としてメソッドがstaticかそうではないかは影響しないことが確認できました。