Build Tools: Maven & Gradle
15 questions found
How would you configure a Maven or Gradle build to produce a reproducible build (bit-for-bit identical output given the same source), and why does this matter for supply chain security?
Advanced
Reproducible builds require eliminating all non-deterministic elements from the build process: fixing file timestamps in the packaged JAR (Maven's <project.build.outputTimestamp> property, tied to a specific commit's timestamp rather than build-time), ensuring consistent file ordering within the archive, pinning exact dependency versions (avoiding version ranges), and using a consistent JDK version -- reproducibility matters for supply chain security because it lets independent third parties verify that a published artifact genuinely corresponds to its claimed source code, detecting any tampering or compromised build infrastructure that might otherwise inject malicious code undetected.
<!-- pom.xml -- enabling reproducible builds -->
<properties>
<project.build.outputTimestamp>2024-01-15T10:00:00Z</project.build.outputTimestamp>
</properties>
# Verify reproducibility by building twice and comparing checksums
mvn clean package
sha256sum target/myapp.jar > build1.sha256
mvn clean package
sha256sum target/myapp.jar > build2.sha256
diff build1.sha256 build2.sha256 # should be identical
Real-world example
An open-source project publishing artifacts to Maven Central configures reproducible builds so that security-conscious downstream consumers can independently rebuild from the published source and cryptographically verify the published JAR matches exactly, providing evidence the artifact wasn't tampered with during the publishing process.
Common follow-ups: What specific non-determinism sources (beyond timestamps) commonly break build reproducibility?;How do supply chain security frameworks like SLSA leverage reproducible builds as one verification layer?
Security Headers
Antiforgery & CSRF Protection;Deploy Checklist
How do you manage different configurations for different environments (development, staging, production) using Maven profiles or Gradle's equivalent mechanisms?
Intermediate
Maven profiles (<profiles> in pom.xml, activated via -P flag, an environment variable, or automatic activation conditions) let you conditionally include different dependencies, properties, or plugin configurations depending on which profile is active -- Gradle typically achieves the same outcome more flexibly through plain conditional logic in the build script itself (checking a project property or environment variable), or through separate source sets/build variants, since Gradle's scripting nature doesn't require a dedicated "profile" abstraction the way Maven's declarative model does.
<!-- Maven profile for production-specific configuration -->
<profiles>
<profile>
<id>production</id>
<properties>
<log.level>WARN</log.level>
</properties>
</profile>
</profiles>
# Activate explicitly
mvn package -Pproduction
Real-world example
A CI/CD pipeline activates a different Maven profile depending on the deployment target (dev, staging, production), each profile substituting the appropriate database connection properties and logging configuration into the packaged artifact without maintaining entirely separate pom.xml files per environment.
Common follow-ups: What are the risks of baking environment-specific configuration directly into the build artifact versus externalizing it at deploy/runtime instead?;How does Gradle's build variant / source set approach compare structurally to Maven profiles?
Configuration & Options Pattern;Deploy Checklist
What is the standard Maven directory layout, and why does following this convention matter?
Beginner
Maven's convention-over-configuration philosophy defines a standard project structure: src/main/java for application source code, src/main/resources for non-code resources bundled into the artifact, src/test/java and src/test/resources for the equivalent test-scoped files, and target/ for build output -- following this convention means Maven's built-in plugins work correctly with zero explicit configuration, whereas deviating from it requires manually reconfiguring source directories in the pom.xml, adding unnecessary complexity for little benefit in most projects.
my-project/
src/
main/
java/ <- application source code
resources/ <- config files, bundled into the JAR
test/
java/ <- test source code
resources/ <- test-only config files
target/ <- compiled classes and packaged JAR go here
pom.xml
Real-world example
A new team member familiar with Maven conventions can immediately navigate any Maven-based project's source layout without reading any project-specific documentation, since src/main/java, src/test/java, and pom.xml are universally consistent across virtually all Maven projects.
Common follow-ups: How would you configure Maven to use a non-standard source directory layout, and what plugin settings does that require?;How does Gradle's default directory convention compare to Maven's?
Java Fundamentals: Syntax
Data Types & Operators;Testing Strategy
How do you configure the Maven Compiler Plugin to target a specific Java language version, and what's the difference between source, target, and release configuration?
Intermediate
The maven-compiler-plugin's <source> and <target> properties independently control the Java language level accepted by the compiler and the bytecode version produced respectively (historically requiring both to be set consistently); the newer <release> property (Java 9+) is preferred since it sets both simultaneously AND ensures the compiler checks against the correct API surface for that specific version (preventing accidental use of APIs only available in a newer JDK than the target, which source/target alone don't fully guard against).
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>17</release> <!-- preferred over separate source/target -->
</configuration>
</plugin>
Real-world example
A team building with JDK 21 installed but targeting Java 17 compatibility for their deployed environment uses <release>17</release>, which correctly rejects any accidental usage of a Java 18+ only API, an error that using only <source>17</source><target>17</target> might not have caught.
Common follow-ups: What error occurs if you compile with a newer JDK using an API not available in your target release, and how does <release> specifically catch this?;What's Gradle's equivalent sourceCompatibility/targetCompatibility versus toolchain configuration?
Java Fundamentals: Syntax
Data Types & Operators;Deploy Checklist
How would you configure a Maven or Gradle build to produce a shaded/fat JAR containing all dependencies bundled into a single executable JAR file?
Advanced
Maven's maven-shade-plugin (or Gradle's Shadow plugin) merges the compiled application classes together with the classes from every dependency JAR into one self-contained "fat" or "shaded" JAR, additionally configuring a manifest Main-Class entry so the result can be run directly via java -jar -- this is valuable for simple deployment scenarios (avoiding needing to separately manage a classpath of many individual dependency JARs), though it can introduce dependency conflicts if multiple bundled dependencies each contain resources at the same path (requiring explicit merge strategies), and produces a notably larger artifact than a thin JAR relying on an external classpath.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
Real-world example
A command-line utility distributed to end users is packaged as a single shaded JAR bundling all its dependencies, letting users run it with a single `java -jar tool.jar` command without needing to separately download and manage a classpath of a dozen individual dependency JARs.
Common follow-ups: What merge strategy conflicts commonly arise when shading multiple dependencies that each bundle a META-INF/services file at the same path?;When would a thin JAR with an external classpath be preferable to a shaded fat JAR?
Deploy Checklist;Hosting Models: Kestrel
IIS & Reverse Proxies