Journal
Progress
Section titled “Progress”- Lesson 1 — The JDK and build tools
- Lesson 2 — Types, equality and operators
- Lesson 3 — Classes, records and enums
- Lesson 4 — Generics and type erasure
- Lesson 5 — Exceptions,
nullandOptional - Lesson 6 — Lambdas and functional interfaces
- Lesson 7 — Collections and Streams
- Lesson 8 — Pattern matching
- Lesson 9 — Concurrency and virtual threads
- Lesson 10 — Maven and Gradle in depth
- Lesson 11 — Testing
- Lesson 12 — The standard library you reach for
- Lesson 13 — The JVM at run time
- Lesson 14 — Annotations, reflection and modules
2026-09-13 — Lessons 1 to 4
Section titled “2026-09-13 — Lessons 1 to 4”- Toolchain on my machine: OpenJDK
25+36, Maven 3.9.16, Gradle 9.7.1, and the .NET 10 SDK for the C# side. CI uses Eclipse Temurin 25 on Windows, Ubuntu and macOS. - The course code lives in
code/java-for-csharp:- every example’s output is compared with the lesson by a JUnit test;
- every rejected snippet is compiled through the
javax.toolsAPI and must produce the javac diagnostic key the lesson quotes (for examplecompiler.err.not.exhaustive) — the Java counterpart of Rust’sE0xxxcodes; - exercise solutions are tests;
- a small .NET 10 program prints the C# side of each comparison.
Surprises coming from C#:
Integer a = 128, b = 128; a == bisfalse, but with 127 it istrue. The cache range −128 to 127 is not an implementation detail: the JLS requires it.java -jaron a freshly built Maven JAR fails withno main manifest attribute. Nothing indotnet buildprepares you for an artefact that doesn’t know its own entry point.- A C#
enumaccepts(Size)42; a Java enum cannot hold a value it doesn’t declare. Java enums are much closer to a sealed set of singleton objects. - javac error messages are sometimes indirect: creating an inner class from a static method reports
non-static variable this cannot be referenced from a static context.
Things I got wrong first:
- In a code comment I called
final var“like a C# readonly local”. C# has no readonly locals. The comment now says so. - I expected
javac -XDrawDiagnosticsand the compiler API to report the same diagnostic key. They don’t always: for adding to aList<? extends Number>, the command line reportscompiler.err.cant.apply.symbolswhile the API reports the simplifiedcompiler.err.prob.found.req. The tests use the API, so the lessons quote what the API sees. - Several lessons first showed an excerpt of a rejected snippet next to an error message whose line number referred to the whole file. The lessons now show the complete file.
- The
‰sign in an example printed as�in the Windows console. The example now writes “per mille”.
Practical cases in my repositories
Section titled “Practical cases in my repositories”Each case applies a lesson to a public repository, at a pinned commit.
GuitarAlchemist/ga — the JetBrains plugin build (lesson 1)
Section titled “GuitarAlchemist/ga — the JetBrains plugin build (lesson 1)”jetbrains-plugin/ at ga@5560b883 is a Gradle Kotlin DSL project (the plugin itself is written in Kotlin). I copied it and tried to build it on this machine.
-
The wrapper is incomplete. Only
gradle/wrapper/gradle-wrapper.propertiesis committed.gradlew,gradlew.batandgradle-wrapper.jarare missing, so./gradlewdoes not exist and every contributor needs a matching Gradle installed.gradle wrapperregenerates the three files, and they should be committed. -
Gradle 8.5 cannot run on JDK 25. With the pinned Gradle 8.5 and JDK 25,
gradle helpfails with a one-word message:* What went wrong:25The stack trace shows
java.lang.IllegalArgumentException: 25thrown while Gradle compiles the Kotlin build script: that Gradle version’s embedded Kotlin compiler doesn’t know Java 25. The fix is either a JDK that Gradle 8.5 supports or a newer Gradle; the Gradle compatibility matrix lists which Gradle version runs on which Java. -
Upgrading Gradle alone is not enough. With Gradle 9.7.1 the build gets further and fails in the legacy
org.jetbrains.intellij1.16.1 plugin:class org.jetbrains.intellij.MemoizedProvider overrides final method org.gradle.api.internal.provider.AbstractMinimalProvider.toString()Ljava/lang/String;That plugin was replaced by the IntelliJ Platform Gradle Plugin 2.x, which uses a different DSL. Migrating is a real change, not a version bump.
-
Gradle’s cache directory is committed. Twelve files under
jetbrains-plugin/.gradle/(lock files, checksums,last-build.bin) are in the repository, the Gradle equivalent of committingobj/. It belongs in.gitignore. -
Java target without a toolchain. The script sets
sourceCompatibility = "17"on eachJavaCompiletask instead of ajava { toolchain { … } }block, so the JDK used depends on the machine that runs Gradle — exactly the variability lesson 1’s toolchain note describes.
Next step, to verify: generate the wrapper, migrate to the 2.x plugin, and check that the plugin still loads in a current IntelliJ IDEA.
spareilleux/learn — the Spring Boot example of the WSL course (lesson 1, exercise 2)
Section titled “spareilleux/learn — the Spring Boot example of the WSL course (lesson 1, exercise 2)”code/wsl-containers/java-reactor-api has no maven-jar-plugin configuration, yet java -jar target/app.jar works. Building it shows why:
Main-Class: org.springframework.boot.loader.launch.JarLauncherStart-Class: dev.learn.reactorapi.JavaReactorApiApplicationThe spring-boot-maven-plugin repackages the JAR: the manifest’s Main-Class is Spring Boot’s launcher, which reads Start-Class and loads the 62 dependency JARs nested under BOOT-INF/lib/ (35 MB in total). It answers both halves of exercise 2 — the missing entry point and the missing dependencies. The <parent> element (spring-boot-starter-parent) is also why the POM has no plugin or dependency versions: it plays the role of Directory.Packages.props and the SDK’s defaults. Lesson 10 comes back to it.
Open questions
Section titled “Open questions”- Is Maven 4 worth teaching yet, given that most projects still use 3.9? To verify: its release status.
- How do IntelliJ IDEA’s inspections compare with javac’s
-Xlint:allfor the traps in lessons 2 and 3?