Tic-Tac-Toe in Java deliberately over-engineered to apply features of Java introduced over time.
Developed to pair with the ongoing blog post: Road to JDK 25 - Over-Engineering Tic-Tac-Toe also serialized to Medium @ Road to JDK 25 - Over-Engineering Tic-Tac-Toe On Medium
The following algorithms are used by the AI BOT in this project - for a detailed discussion see Road to JDK 25 - An Algorithmic Interlude:
- Random See: Random.java
- Minimax See: Minimax.java
- Minimax w. Alpha-Beta See: AlphaBeta.java
- MaxN See: MaxN.java
- Paranoid See: Paranoid.java
- Monte Carlo Tree Search See MonteCarloTreeSearch.java
https://openjdk.org/projects/jdk/25/
- JEP512:   Compact Source Files and Instance Main Methods
- See: AppLite.java — compact source file with a top-level main.
- How to run: run_lite.sh
 
- See: AppLite.java — compact source file with a top-level 
- JEP511:   Module Import Declarations
- See: AppLiteHttp.javausesimport module java.net.http;to simplify imports while using the JDK HTTP client.- File: AppLiteHttp.java
- How to run: run_lite_http.sh
 
- Example (jshell):
import module java.net.http; var client = HttpClient.newHttpClient(); var res = client.send(HttpRequest.newBuilder(java.net.URI.create("https://example.com")).build(), HttpResponse.BodyHandlers.ofString()); System.out.println(res.statusCode()); 
 
- See: 
- JEP513:   Flexible Constructor Bodies
- Allows statements before super(...). Example: GameServiceException.java validatescausebeforesuper(message, cause).
 
- Allows statements before 
- JEP506:   Scoped Values (See: Game.java, GameTest.java)
- Uses ScopedValueto bind a per-playGameContext(see: GameContext.java) withidandcreatedAt, scoped strictly toplay()execution; verified in tests.
 
- Uses 
- JEP514:   Ahead-of-Time Command-Line Ergonomics (See: 2a_aot_record_create_one_step.sh)
- One-step AOT workflow using -XX:AOTCacheOutput=app.aotto record and create in a single invocation. Production runs use-XX:AOTCache=app.aot(see: 3a_aot_run.sh).
- Optional: Set JDK_AOT_VM_OPTIONSto pass options that apply only during the cache creation phase.
 
- One-step AOT workflow using 
- JEP510:   Key Derivation Function API (HKDF)
- Use HKDF-SHA256 (javax.crypto.KDF) to derive AES-GCM keys from the Kyber KEM secret (no raw slicing).
- See: SecureDuplexMessageHandler.java
 
- Use HKDF-SHA256 (
https://openjdk.org/projects/jdk/24/
- JEP496:   Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism (See: KyberKEMSpi.java, SecureConnectionTest.java)
- Implements ML-KEM via JCE SPI and uses BouncyCastle PQC; tests establish secure client/server handshake.
 
- JEP485:   Stream Gatherers (See: Analyzers.java, StrategicTurningPoint.java)
- Custom Gathererdiscovers strategic turning points while streamingGameStatehistory.
 
- Custom 
- JEP483:   Ahead-of-Time Class Loading & Linking (See: app/scripts/, e.g., 1_aot_record.sh, 2_aot_create.sh, 3a_aot_run.sh)
- Scripts record, generate, and run with AOT class data to improve startup.
 
- JEP484:   Class-File API (See: NaiveFifoGenerator.java)
- Uses the java.lang.classfileAPI to generate a simple bot class at runtime.
 
- Uses the 
https://openjdk.org/projects/jdk/23/
- JEP467:   Markdown Documentation Comments	(See: Player.java)
- Uses Markdown-friendly documentation with @snippetto embed example usage.
 
- Uses Markdown-friendly documentation with 
- JEP474:   ZGC: Generational Mode by Default
- Example: Configure ZGC via JVM args in Gradle
- App: app/build.gradle.kts — applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "-XX:+UseZGC")
- TCP Game Server tests: tcp-gameserver/build.gradle.kts — jvmArgs = listOf("--enable-native-access=ALL-UNNAMED", "-XX:+UseZGC")
 
- App: app/build.gradle.kts — 
 
- Example: Configure ZGC via JVM args in Gradle
- JEP471:   Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal
- Not used; examples with standard APIs: FFM (TicTacToeGameBoard.java, TicTacToeLibrary.java), Cleaner (TicTacToeGameBoard.java), VarHandles (PlayerIds.java).
 
https://openjdk.org/projects/jdk/22/
- JEP454:	Foreign Function & Memory API (See: TicTacToeLibrary.java, TicTacToeGameBoard.java)
- Calls native functions via Linker/SymbolLookup, usesMemorySegmentand upcall stubs.
 
- Calls native functions via 
- JEP456:	Unnamed Variables & Patterns (See: PlayerPrinter.java, GameClient.java)
- Demonstrates unnamed catch variables: e.g., catch (UnknownHostException _)andcatch (ConnectException _).
 
- Demonstrates unnamed catch variables: e.g., 
https://openjdk.org/projects/jdk/21/
- JEP431:	Sequenced Collections (See: history()in Game.java)- Uses SequencedCollection<GameState>for ordered game history.
 
- Uses 
- JEP439:	Generational ZGC
- Runtime/flag-level feature; no code changes required here.
 
- JEP440:	Record Patterns (See: GameContextTest.java)
- Demonstrates record pattern deconstruction and guarded cases in a switchoverGameContext.
 
- Demonstrates record pattern deconstruction and guarded cases in a 
- JEP441:	Pattern Matching for switch (See: handleException(...)in GameServer.java)- Uses a switchwith type patterns to handle root causes (e.g.,SocketTimeoutException).
 
- Uses a 
- JEP444:	Virtual Threads (See: GameServer.java)
- Handles many concurrent games using Thread.ofVirtual()andExecutors.newThreadPerTaskExecutor.
 
- Handles many concurrent games using 
- JEP452:	Key Encapsulation Mechanism API (See: KyberKEMSpi.java)
- Implements the JCE KEM SPI to integrate ML-KEM with the standard crypto API.
 
https://openjdk.org/projects/jdk/20/
- No new features
https://openjdk.org/projects/jdk/19/
- No new features
https://openjdk.org/projects/jdk/18/
- JEP400:	UTF-8 by Default
- Source files and runtime default charset assume UTF-8; no special handling required.
 
- JEP408:	Simple Web Server (See: serve.sh)
- Uses JDK's built-in jwebservervia helper script to serve static files locally; see Quick Start below.
 
- Uses JDK's built-in 
- JEP413:	Code Snippets in Java API Documentation (See: Player.java)
- Uses @snippetto embed example usage directly in Javadoc.
 
- Uses 
- JEP421:	Deprecate Finalization for Removal (See: TicTacToeGameBoard.java)
- Avoids finalization in favor of Cleanerfor native resource management.
 
- Avoids finalization in favor of 
https://openjdk.org/projects/jdk/17/
- JEP421:	Deprecate Finalization for Removal (See: TicTacToeGameBoard.java)
- Uses Cleanerover finalization for native resource management.
 
- Uses 
- JEP409:	Sealed Classes (See: StrategicTurningPoint.java)
- StrategicTurningPointis a sealed interface with record implementations.
 
- JEP410:	Remove the Experimental AOT and JIT Compiler
- Alternative: Use GraalVM Native Image via Gradle (See: app/build.gradle.kts — org.graalvm.buildtools.native)- Example tasks: ./gradlew :app:nativeCompile,./gradlew :app:nativeRun
 
- Example tasks: 
 
- Alternative: Use GraalVM Native Image via Gradle (See: app/build.gradle.kts — 
- JEP415:	Context-Specific Deserialization Filters (See: GamePersistence.java)
- Applies an ObjectInputFilterbefore deserialization and uses guarded checks.
 
- Applies an 
- 
To run the single game application, use the following command: ./gradlew run
- 
To run the lite HTTP demo (JEP 511 + java.net.http), use: app/scripts/run_lite_http.sh
- 
If you don't have Java installed on your system you can install it first with SDKMAN to build with a JDK 25 toolchain: 
curl -s "https://get.sdkman.io" | bash
sdk install java 25-zulu
./gradlew run- Requires JDK with GraalVM Native Image tooling. This project is configured with the Gradle plugin org.graalvm.buildtools.nativeinapp/build.gradle.kts.
- Build and run the native image:
./gradlew :app:nativeCompile
./gradlew :app:nativeRun- 
Requires a JDK with AOT cache support. For the one-step workflow (JEP 514), use JDK 25+. 
- 
One-step record+create (JDK25+): 
# Create AOT cache in a single invocation (produces app.aot)
app/scripts/2a_aot_record_create_one_step.sh
# Run with the generated AOT cache
app/scripts/3a_aot_run.sh- Two-step workflow (works on JDK24+):
# 1) Record training configuration
app/scripts/1_aot_record.sh
# 2) Create the AOT cache (produces app.aot)
app/scripts/2_aot_create.sh
# 3) Run with the AOT cache
app/scripts/3a_aot_run.sh- Optional (JEP 514): Pass flags only for the create phase by setting JDK_AOT_VM_OPTIONS:
export JDK_AOT_VM_OPTIONS="-Xms512m -Xmx512m"
app/scripts/2a_aot_record_create_one_step.sh- Benchmark (optional): Requires hyperfine(see project page: https://github.com/sharkdp/hyperfine). Ensure you've created the AOT cache first using the steps above (one-step or two-step), then run:
hyperfine -n standard './scripts/bench.sh' -n aot './scripts/bench.sh --aot'- Serve the project directory (e.g., to host index.html) using JDK’s built-injwebservervia the helper scriptserve.sh(wrapsjwebserver). Usage:serve.sh <port> <directory>:
# Serve current directory on port 8000
app/scripts/serve.sh 8000 .
# then open http://localhost:8000- Over-Engineering Tic-Tac-Toe - CLI Run overengineered tic-tac-toe from the command line
- Over-Engineering Tic-Tac-Toe - Microservices Microservice-based version of the project
