Saltar a contenido

Week 2 - Build Tools, Dependencies, and JPMS

Conceptual Core

You will live in Maven or Gradle for the rest of your career. Pick one and learn it deeply; the other is then 80% transferable. Switching mid-project loses 2-4 weeks of velocity - pick once.

JPMS (Java Platform Module System, JEP 261) is the thing every Java developer knows exists and nobody outside library authors uses. Most applications never need it. The tax: recognizing when a module-system issue (an --add-opens requirement, an "unnamed module" warning, a missing requires in module-info.java) explains a failure you'd otherwise spend hours debugging.

Mechanical Detail

  • Maven lifecycle: validate → compile → test → package → verify → install → deploy. Each phase runs itself plus all earlier ones. mvn package is the daily driver. mvn dependency:tree shows transitives; mvn dependency:analyze flags declared-but-unused and used-but-undeclared deps.
  • Maven inheritance: every project inherits from super-pom. Multi-module projects add a parent pom for shared config. BOMs (e.g. spring-boot-dependencies) manage versions of an artifact constellation; import via <dependencyManagement> + <scope>import</scope>.
  • Gradle: task graph (gradle X runs X's dependency tasks first). The configuration cache (org.gradle.configuration-cache=true) skips reading build.gradle on unchanged builds - large speedup, but breaks plugins that do I/O at configuration time. Version catalogs (gradle/libs.versions.toml) centralize versions across modules.
  • JPMS: module-info.java declares requires, exports, opens. requires transitive re-exports a dependency. opens permits reflective access (Jackson, Spring, JPA need it). Two failure symptoms: "illegal reflective access" (needs opens or --add-opens); "module X not found" (needs requires or to be on the module path).
  • jdeps Yourapp.jar lists modules needed; jlink --add-modules <list> --output runtime --strip-debug --no-man-pages produces a 35-50MB JRE instead of the 200MB stock JDK. Useful for containers.

The trap

Not pinning Maven plugin versions. The default resolves "whatever is latest available" - a build that worked yesterday may behave differently today. Always pin <version> in pluginManagement and let Renovate update them deliberately.

Lab

Take any small library. Modularize it: write module-info.java, run jdeps to find required modules, jlink --add-modules <list> --output runtime/ to build a custom runtime. Measure size before (du -sh $JAVA_HOME) vs after (du -sh runtime/). Then mvn install to a local repo and consume from a separate Gradle project via mavenLocal().

Idiomatic Drill

Read 10 random pom.xml files from popular libraries (Caffeine, Resilience4j, Micrometer, Jackson). Spot the patterns: BOMs via <dependencyManagement>, explicit <scope> (compile / test / provided / runtime), plugin versions in pluginManagement not inline.

Production Hardening Slice

For reproducible builds, lock down: - Java version via <maven.compiler.release>21</...> (or Gradle JavaLanguageVersion.of(21)). - Parameter names via -parameters (Maven <maven.compiler.parameters>true</...>) so Spring / Jackson can read them at runtime. - Every plugin version in pluginManagement. - Renovate or Dependabot for dependency PRs.

Comments