Build Tools
Maven vs Gradle, the Maven lifecycle, dependency scopes, and structuring a multi-module project.
4 questions
JuniorTheoryVery commonWhat do Maven and Gradle do for a Java project, and how do they differ?
What do Maven and Gradle do for a Java project, and how do they differ?
Both resolve dependencies, compile, test and package a project. Maven is declarative: a fixed lifecycle driven by pom.xml, so every build looks alike. Gradle is programmable: a DSL builds a task graph, with incremental builds and a build cache.
Common mistakes
- ✗Thinking a build tool replaces
javacinstead of orchestrating it - ✗Believing Gradle is the declarative one and Maven the scripted one
- ✗Treating them as dependency managers only, ignoring the build lifecycle
Follow-up questions
- →Which lifecycle phases run when you type
mvn test? - →What does Gradle's build cache reuse that a plain Maven build re-runs?
MiddleTheoryCommonWhat do Maven's compile, provided, runtime and test dependency scopes mean?
What do Maven's compile, provided, runtime and test dependency scopes mean?
A scope decides which classpaths a dependency lands on. compile, the default, is on all of them and ships in the artifact. provided is compiled against but supplied at runtime by the container. runtime is needed only to run; test only by test code.
Common mistakes
- ✗Assuming a
provideddependency is packaged into the artifact - ✗Using
compilefor the servlet API or a JDBC driver out of habit - ✗Thinking a
test-scoped library is importable from production code
Follow-up questions
- →Which scope fits the servlet API when you deploy to an external Tomcat?
- →How does a
provideddependency behave transitively in a downstream module?
MiddleTheoryCommonWhat is the difference between mvn package and mvn install?
What is the difference between mvn package and mvn install?
Maven's lifecycle is cumulative: install runs everything package does and one phase more. package compiles, tests and writes the artifact into target/. install also copies it into the local repository ~/.m2, so other projects can depend on it.
Common mistakes
- ✗Thinking
packagealready publishes the artifact to the local repository - ✗Believing
installpushes to a remote repository — that isdeploy - ✗Assuming the two phases are independent rather than cumulative
Follow-up questions
- →Which phase publishes an artifact to a shared remote repository?
- →Why does a dependent project on the same machine sometimes need
installfirst?
MiddleDesignOccasionalA single Maven module has grown to hold REST controllers, domain services and JPA persistence code, plus a client SDK that two other teams copy-paste. Every change recompiles and retests the whole thing, and domain classes have started importing controller classes. You must restructure it into a multi-module build under these constraints: the client SDK ships as its own artifact without dragging in server dependencies; dependency versions stay aligned across modules; a change in one module must not force a full rebuild of the rest; and the layering has to be enforced by the build, not by convention. How would you lay out the modules and their dependency direction, and what stops the layering from being violated again?
A single Maven module has grown to hold REST controllers, domain services and JPA persistence code, plus a client SDK that two other teams copy-paste. Every change recompiles and retests the whole thing, and domain classes have started importing controller classes. You must restructure it into a multi-module build under these constraints: the client SDK ships as its own artifact without dragging in server dependencies; dependency versions stay aligned across modules; a change in one module must not force a full rebuild of the rest; and the layering has to be enforced by the build, not by convention. How would you lay out the modules and their dependency direction, and what stops the layering from being violated again?
Split by layer: domain, persistence, web and a standalone client-sdk, under a parent POM pinning shared versions. Dependencies point inward only, so a domain class importing a controller stops compiling. Each module ships as its own artifact.
Common mistakes
- ✗Splitting by technical kind (interfaces/impl) instead of by layer
- ✗Relying on package naming instead of module dependencies to enforce layering
- ✗Declaring versions per child module instead of in the parent POM
Follow-up questions
- →Why is a dependency cycle between two Maven modules impossible to build?
- →How would you keep the SDK module free of the server's transitive dependencies?