Skip to main content

JVM Compilation and Builds

If you're going to build anything serious in the Java ecosystem, it helps to understand what happens to your code after you hit save. You know the languages (Java, Kotlin, Groovy) and you know they all compile to bytecode that runs on the JVM. But what happens in between? That's where build tools and the compilation process come in.

Build Tools

Build tools are project managers. They handle the heavy lifting:

  • Download libraries/dependencies
  • Define steps (compile code, run tests, build JAR files)
  • Keep project structure standard and organized

You'll typically encounter Maven or Gradle. For most Spring Boot projects, it doesn't matter which one you pick. They both get you to the exact same destination.

AspectMavenGradle
ConfigurationUses XML (structured with <tags>)Uses Kotlin/Groovy (code-like syntax)
FlexibilityStrict, standardized conventionsHighly customizable (supports logic like if-else)
Use CasesLegacy or enterprise Java projectsAndroid apps, modern Java/Kotlin projects

Both download from Maven Central, both compile the same way, both produce the same JARs.

The Build Folder

Maven calls it target/, Gradle calls it build/. Doesn't matter. It's your build output directory, and it contains:

DirectoryPurpose
classes/java/main/Compiled production classes
classes/resources/Resources bundled with JAR
test-results/Test execution results
generated-sources/Code generated by annotation processors (Lombok, MapStruct, etc.)
my-app.jarYour production artifact

The build folder exists, but for day-to-day development, you rarely need to go there. There are rare moments when you'll want to explore the build folder:

  1. Debugging test failures: Open test-results/ to see XML reports and understand what went wrong
  2. Understanding generated code: Check generated-sources/ when annotation processors like Lombok are creating code you don't understand
  3. Investigating mysterious CI failures: Verify the build folder contains exactly what you expect
  4. Running manual tests: You can directly run java -jar build/my-app.jar without build tools

JAR

Your production JAR doesn't have fingerprints for Maven or Gradle. They're identical from the JVM's perspective.

  1. Dependency management is identical: The difference is syntax (pom.xml vs build.gradle), not behavior.
  2. Generated JARs are twins:
    • Same class layout
    • Identical MANIFEST.MF file
    • Same resource files
    • Same metadata
  3. IDEs don't care: IntelliJ IDEA auto-detects both and shows the appropriate Maven or Gradle toolbar.

Bytecode

Even though Java, Kotlin, and Groovy all compile to JVM bytecode, the quality and characteristics of that bytecode differ significantly.

Scroll to zoom • Drag corner to resize
AspectJavaKotlinGroovy
CompilationDirect to bytecodeDirect to bytecode, then optimizedDynamic compilation, generates verbose bytecode
OverheadNone (when compiled)Minimal (no runtime overhead)High (dynamic features add runtime checks)
AST Transforms❌ None❌ None✅ Yes (via @Canonical, @Builder, etc.)
Type SafetyCompile-timeCompile-time (with null-safety)Runtime (dynamic typing)
Bytecode SizeCompactCompactBulky (extra frames, checks, proxies)
VisibilityClear in IDEClear in IDEOften invisible (generated code)

What Are AST Transformations?

AST (Abstract Syntax Tree) transformations are compile-time code generation mechanisms that modify your source code before it becomes bytecode. They're powerful features that generate boilerplate code for you.

Common examples in Groovy:

  • @Canonical → automatically generates equals, hashCode, toString methods
  • @Builder → generates a complete Builder pattern with all the necessary methods
  • @Slf4j → generates a logger instance for logging

These transformations generate actual Java code that gets compiled alongside your source files. You can't see it in your editor, but it exists in the build folder.