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.
| Aspect | Maven | Gradle |
|---|---|---|
| Configuration | Uses XML (structured with <tags>) | Uses Kotlin/Groovy (code-like syntax) |
| Flexibility | Strict, standardized conventions | Highly customizable (supports logic like if-else) |
| Use Cases | Legacy or enterprise Java projects | Android 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:
| Directory | Purpose |
|---|---|
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.jar | Your 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:
- Debugging test failures: Open
test-results/to see XML reports and understand what went wrong - Understanding generated code: Check
generated-sources/when annotation processors like Lombok are creating code you don't understand - Investigating mysterious CI failures: Verify the build folder contains exactly what you expect
- Running manual tests: You can directly run
java -jar build/my-app.jarwithout build tools
JAR
Your production JAR doesn't have fingerprints for Maven or Gradle. They're identical from the JVM's perspective.
- Dependency management is identical: The difference is syntax (
pom.xmlvsbuild.gradle), not behavior. - Generated JARs are twins:
- Same class layout
- Identical
MANIFEST.MFfile - Same resource files
- Same metadata
- 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.
| Aspect | Java | Kotlin | Groovy |
|---|---|---|---|
| Compilation | Direct to bytecode | Direct to bytecode, then optimized | Dynamic compilation, generates verbose bytecode |
| Overhead | None (when compiled) | Minimal (no runtime overhead) | High (dynamic features add runtime checks) |
| AST Transforms | ❌ None | ❌ None | ✅ Yes (via @Canonical, @Builder, etc.) |
| Type Safety | Compile-time | Compile-time (with null-safety) | Runtime (dynamic typing) |
| Bytecode Size | Compact | Compact | Bulky (extra frames, checks, proxies) |
| Visibility | Clear in IDE | Clear in IDE | Often 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 generatesequals,hashCode,toStringmethods@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.