Skip to main content
Introduction to Devops

Automating the Build Process with Maven and Gradle

Published: 2026-08-14
Level: postgraduate
Audience: Postgraduate students of software engineering and delivery

Prerequisite Knowledge

This lecture builds on the following concepts from earlier lectures. If any feel unfamiliar, review the linked notes before proceeding.

Previously Covered in This Subject

  • Version control with Git and GitHub — covered in Lecture 7 (Git, GitHub, and Version Control)
  • Branching and workflows (Git flow vs GitHub flow) — covered in Lecture 7
  • The continuous integration server and continuous testing — covered in Lecture 1
  • Continuous integration and automated testing — covered in Lecture 4 (DevOps and the Agile Lifecycle)
  • The build pipeline and Jenkins build triggers — covered in Lecture 5
  • Delivery, deployment, and release — covered in Lectures 1 and 2

Automating the Build Process with Maven and Gradle

The previous session covered version control with Git and GitHub — how the two are related and why a version control system exists at all. Once you have version control in place, the next thing to think about is automating the build process. We write source code to build it and generate an artifact out of it — an exe file, a PDF, anything that lets you get your artifact out of your application. So the sequence is: write the source code, then the next phase is to build it. This session is about automating that build process by assembling software components with the help of build tools, and as per the syllabus the tools in focus are Maven and Gradle.

Think of the whole journey like a production line: version control is the raw-material warehouse (the previous session), and this session is about the assembly line that turns those raw materials into a finished, shippable product. Before any machine can assemble parts automatically, the parts themselves have to be designed and organized — which is exactly why this session starts with component-based design, then moves through dependencies, pipelines, and finally the two build tools (Maven and Gradle) that automate the assembly itself.

8.1 Component-Based Design: Why and How

Hook. How do large software teams — tens or hundreds of developers — work on one codebase without stepping on each other? And how does a company ship a grocery app with login, payments, and search all changing at different speeds? The answer begins with one design idea: component-based design — splitting the application into pieces that can be understood, changed, and released independently.

8.1.1 What a Component Is and How It Gets Packaged

Whenever you have a large code structure within an application, those code structures are referred to as modules. A component is nothing but a module, and by putting multiple modules together you get your large-scale application.

A component is a large-scale code structure inside an application that has a well-defined interface (an API) and exposes behavior through limited, well-defined interactions with other components. In plain terms: each component does one coherent job — say, "handle payments" — and other parts of the application talk to it only through its API, never by reaching into its internals. "Component" is a heavily overloaded word in software; the textbook uses the terms "module" and "component" as synonyms, and clarifies that the idea has nothing to do with GUI widgets — it is about how you organize code, not which framework you use.

The packaging of a component depends on the platform. Real-world: on Windows a component is normally packaged as a DLL library; on Unix it is referred to as a .so (shared object) file; in the Java world it starts being referred to as a jar — a JAR file. These are all just ways of saying the same thing: components are the modules of your application. The names differ because each platform has its own standard mechanism for bundling compiled code so other programs can load it: Windows dynamic-link libraries (DLLs), Unix shared libraries (.so files), and Java Archives (JARs).

Component-based design helps you arrange this large-scale code structure in a modular way so that the code is easy to understand, and whenever you want, you can easily make changes. That modular arrangement is the foundation everything else in this session builds on. Notice the contrast with the monolithic system — its antithesis — which has no clear boundaries between elements responsible for different tasks, poor encapsulation, and tight coupling between logically independent parts. The textbook's formal definition of a component adds four properties most people agree on: a component is reusable, replaceable with something else implementing the same API, independently deployable, and encapsulates a coherent set of behaviors and responsibilities.

Packaging by platform — a mini worked example. Suppose your team builds the cart module of an app in C# on Windows: the compiled component ships as cart.dll. A colleague ports the same design to a Linux server: the same component ships as cart.so. A third team reimplements the identical behavior in Java: it ships as cart.jar. Three artifacts, one component concept — the packaging format follows the platform, not the design. Final answer: DLL (Windows), .so (Unix), JAR (Java). Sense-check: each file bundles the compiled component so other programs can load it, which is exactly what a component package is for.

8.1.2 Benefits of Component-Based Design

The benefits the design brings are threefold.

  1. Reuse of code. A component is written once and reused wherever its service is needed — the login component serves the web app, the mobile app, and the admin panel without being rewritten.
  2. A good architectural diagram — loose coupling. Components talk only through well-defined interfaces, so the architecture ends up loosely coupled: changing the inside of one component does not ripple through the others. This loosely coupled structure is the pilot point, the initial point, for microservice architecture — microservices are, in essence, components that are deployed and scaled independently, often as separate running services.
  3. Efficient large-team collaboration. This is the textbook's key practical point: componentization is one of the most efficient ways for a large team of developers to collaborate on one application. Each team can understand and own one part of the codebase, and a change to one component does not force everyone else to re-understand the whole system.

Intuition. Think of a component like an engine part in a car factory. The engine module is built by one team, the infotainment module by another. Each has a standard mounting point (the API), each can be upgraded or swapped independently, and the factory can reuse the same engine in several car models. The analogy breaks where software is softer than metal: a component's API can be changed by the team that owns it, so dependencies must be managed carefully — which is exactly the challenge of Section 8.1.3.

8.1.3 The Challenge: Components Form Dependencies

When you decompose your application into components — and decomposing is the biggest job — those several components ideally form a series of dependencies among themselves. Take the example used throughout the session: a web application called e-grocery (a grocery delivery app). For the login page of that application you need four modules: one for the login service, one for the payment gateway, one for search, and one for adding and retrieving the grocery items — grocery item management. These four modules definitely form a series of dependencies.

Worked example — the e-grocery dependency chain. Consider the four modules:

  • Login service — authenticates users.
  • Payment gateway — charges customers.
  • Search — lets users find products.
  • Grocery item management (the cart) — add, retrieve, delete, and manage the items in a customer's cart.

The payment gateway is definitely dependent on the grocery items calculations: what components or items are available in your cart. Only after that will the payment be successful. Payment cannot be computed before the cart tells it what items, quantities, and prices are involved — you cannot charge for an empty or unknown cart. Similarly, searching an item is basically dependent on login and authentication being successful — only then should the user be able to search anything. Search is only meaningful for an authenticated user, and exposing it to unauthenticated users would leak data and invite abuse. So the dependency arrows run: cart → payment and login → search. Final answer: two clear dependency chains among the four modules. Sense-check: both chains are "must-know-X-before-Y" relationships — exactly the structure of a dependency.

This simple example is how you look at dependency: whenever you design your application with component-based design, it will form a series of dependencies.

Each component may also have several release branches. Why would a component have several release branches? If you chose a payment gateway that currently supports delivery or net banking, and you now want to add UPI payments and credit cards, the payment gateway module will be frequently changed — and in this example the payment gateway goes through several release branches. Even the login and sign-up module changes: you might want to add one extra field, say flat address, local address, and permanent address, so you can start delivering items to the permanent address too — to capture the data of whether a customer is really from the city where the service operates or has another home where you can start advertising. That is part of the business. In that case as well the module will be changed. So whenever code changes in one module, that component will have different release branches.

Worked example — why a payment gateway has several release branches. The gateway begins supporting cash on delivery and net banking (release 1.0). The business adds UPI payments: the module changes and ships release 1.1; then credit cards arrive: release 1.2. Meanwhile the login module evolves from email + password to also capture flat, local, and permanent addresses — the business wants to know whether a customer is a true in-city resident or has a second home where the service could advertise and deliver. Every one of these business changes produces a new version of the affected component. Final answer: the payment gateway in this story ships multiple release branches (1.0 → 1.1 → 1.2) driven by payment-method features; the login module changes for address capture. Sense-check: each release branch captures a state of the component's code at a point in time — which is exactly the raw material the release-delay problem (Section 8.1.4) has to handle.

8.1.4 The Release-Delay Problem

The biggest challenge with component-based design is delay in release. The very first problem is finding the good version of each of these components. Components go through several changes, so several artifacts come out of them; if you do it manually it becomes tedious, and assembling them into a system that even compiles is an extremely difficult process. Done manually, that is the challenging part. To overcome it, you need to follow the best practices and try to automate these steps.

The textbook describes the situation vividly: components form a series of dependencies, which in turn depend on external libraries, and each component may have several release branches. Finding good versions of each component that can be assembled into a system which even compiles is an extremely difficult process that can resemble a game of whack-a-mole — the authors report projects where it takes months. Only once you have done this can you start moving the system through the deployment pipeline. This is, in essence, the fundamental problem that continuous integration aims to solve.

Scope — when does this problem appear? The release-delay problem is a problem of scale. On a small project with one version control repository and a simple pipeline, you can build the whole system in one shot and this section barely matters. The trouble creeps in as projects grow: once a codebase passes a certain threshold, splitting it into components becomes expensive, and the manual "find the right versions and assemble" chore turns into a months-long whack-a-mole game. The lesson: design for components before you need them.

8.1.5 Three Approaches to Keep the Application Releasable

How can one keep an application always in a releasable state? There are three approaches.

Approach 1 — Hide new functionality until it is finished. This is the feature toggling approach: you can toggle between features. Until the functionality is finished, even if the code is residing in the production environment, make that functionality hidden — the toggling flag variable for that particular functionality should be false. If the flag is false, do not access this code. This is done at the code level. The textbook's example: a travel website wants to offer hotel bookings; the new feature is deployed along with the rest of the system, but its entry point is inaccessible — access is blocked by a configuration setting. When the feature is ready, the flag flips to true and the code path opens without a big-bang release.

Approach 2 — Make changes incrementally as a series of small, releasable changes. This looks like a solution, but breaking a module into a small, small series of changes is again a difficult task. You cannot say that you will just implement a login button without the password — you need to be ready with login and password together. Sometimes, through logical or business requirements, it is not feasible to break a module into smaller chunks. So this is one possible solution, only when it is possible. The textbook adds that the analysis behind this approach is the same thought process used to break a requirement into smaller tasks — and when a change genuinely cannot be made incrementally, you should consider branch by abstraction.

Approach 3 — Branch by abstraction. Create a branch for that particular change and do not merge that branch until and unless the code is completed — the functionality is fully completed. This makes sure the application is always in a releasable state because you have not touched the main baseline, which is bug free or defect free. A perfect version, a perfect artifact, remains available. In the textbook's more precise description, you do not actually branch in version control: you create an abstraction layer over the piece to be changed, develop the new implementation in parallel, switch the abstraction to delegate to the new implementation when it is complete, then remove the old implementation and the abstraction layer. Either reading, the core idea is the same: the main baseline is never broken, so the application stays releasable.

So the three approaches are: hide the functionality at the code level; break the change down into smaller chunks; or use branch by abstraction — create a branch and do not merge it until the code is ready.

Pitfalls.

  • Feature flags left on forever. The flag approach works only if flags are pruned once a feature ships. The textbook reports an extreme case where a search-engine company had to patch the Linux kernel just to accept the sheer number of command-line options toggling features — keep the list short and clean it up.
  • Forcing the impossible split. Approach 2 is not always feasible: a login without a password is not a releasable feature. Do not contort the design to force a small change; use another approach instead.
  • Branching for the wrong reason. The bigger a change looks, the more tempting it is to branch — but the bigger the change, the harder the eventual merge, especially while other teams keep working. Branching by abstraction was invented precisely to avoid painful merge-and-wire-up disasters.

Exam note: the three approaches for keeping an application always releasable are a classic application-style question — be ready to describe feature toggling, incremental small changes, and branch by abstraction. A common exam setup gives you a scenario ("the team is building a large new feature over two months — how do they keep shipping?") and expects you to name the approach and say how it works.

Recap. Components are the modules of an application, packaged as DLLs, .so files, or JARs; they bring reuse, loose coupling (the seed of microservices), and large-team collaboration; but they form dependencies, accumulate release branches, and make manual assembly so hard that release gets delayed. Three techniques — feature toggling, incremental changes, branch by abstraction — keep the application releasable. The next section turns to the dependencies themselves: what they are, what kinds exist, and why they cause dependency hell.

Real-world & domain. Every platform you use daily is component-based. An Android app ships as one APK containing many components; Windows system updates swap DLLs between applications; a microservice estate like Netflix or Amazon decomposes an e-commerce system into login, cart, payment, and search services that change at different rates. In e-commerce specifically, the e-grocery chains from this section are the everyday reality: payment providers (Stripe, Razorpay, UPI gateways) release new payment methods on their own cadence, and the merchant's payment component must track those releases — one reason component dependency management is the foundation of modern release engineering.

8.2 Managing Application Dependencies

Hook. Why do applications randomly break after a Windows update? Why did your friend's Java program refuse to run on your machine even though the code was identical? Both are symptoms of one silent force inside every program: dependencies — the other software it quietly relies on to build and run.

8.2.1 What a Dependency Is

Before talking about library-based and component-based dependencies, it helps to define the thing itself. A dependency occurs when one piece of software depends upon another in order to build or run. We say the particular component is dependent on some library or on some other component. Examples are everywhere: every software application depends on the host operating system environment; a Java application depends on the JVM; if you want to start working with Rails you need Ruby and the Rails framework; a C application depends on the C standard libraries. The textbook gives the same catalogue: Java applications depend on the JVM which provides the Java SE API, .NET applications depend on the CLR, Rails applications on Ruby and the Rails framework, C applications on the C standard library, and so forth. In any application beyond the most trivial, there will be some dependencies.

Dependencies can be categorized broadly in two ways: by what you depend on (libraries versus components), and by when the dependency matters (build time versus runtime). Both distinctions are needed because each one changes what a build process has to do.

8.2.2 Library Dependency versus Component Dependency

A library is a software package that your team does not control other than choosing which one to use. The packages themselves are not under your team's control — your only control is opting for a particular one: you choose which libraries you need, like JRE 7 or JRE 8, or a React framework. Libraries are usually updated rarely — there are no frequent changes in libraries.

A component, by contrast, is a piece of software that your application depends upon, but which is also developed by your team or by another team in your organization. Because it is being developed inside the organization, there will be frequent changes. In the grocery application, the cart module — grocery item arrangement, retrieve, delete, add, and managing the cart — is depended upon by the payment gateway; that cart module is a component. Both components are developed within the team, so there will be several branches and frequent changes to these modules.

This distinction is really important because when you design a build process, there are more things to consider when dealing with components than with libraries. Managing library dependencies is the easier case; component dependency management is the harder one — that is why the two must be kept clear. The build-process questions that components force you to answer — do you compile the entire application in a single step, or compile each component independently when it changes? How do you manage dependencies between components and avoid circular ones? — simply do not arise for libraries.

Intuition + analogy. A library is like a public road: the city (someone else) owns and maintains it, and your only decision is which road to take. A component is like your own delivery fleet: your company owns the trucks, so they change frequently — new routes, new boxes, new schedules — and every change affects the shops that depend on them. Where the analogy helps: ownership determines how much change to expect. Where it breaks: libraries can be "removed" without notice too — a library's owner may release a new version that breaks you, which is why the dependency-hell problem (Section 8.2.4) exists at all.

8.2.3 Build-Time and Runtime Dependencies

A build-time dependency must be present when your application is compiled and linked, if necessary. A runtime dependency must be present when the application runs and performs its usual function. A simple example: a Maven-based project depends on the Tomcat server. At the time you run that application, you have to make sure the Tomcat server instance is up and running — that running Tomcat server is a runtime dependency for the application. In C and C++, the build-time dependencies are simply your header files, while at runtime your program requires a binary to be present in the form of a DLL or shared library, on Windows or Linux respectively.

The textbook underlines two practical consequences of this distinction. First, your deployment pipeline itself uses many pieces of software that are irrelevant to the deployed copy of the application — unit test frameworks, acceptance test frameworks, build scripting frameworks. Those are build-time concerns only. Second, the versions of libraries used at build time can differ from those used at runtime: in compiled languages you can build against a JAR containing just the interfaces of a system and run against a JAR with a full implementation (the J2EE application server pattern). Your build system must take both kinds of dependencies into account.

8.2.4 The Dependency Hell Problem

Managing these different dependencies can be difficult — this is the classic dependency hell problem, also called DLL hell, which used to happen very frequently before version control systems existed. In the old days, when you ran the software you got an artifact; those libraries used to be stored somewhere in a shared folder so other modules dependent on that DLL or library could access them. Sometimes at runtime the application used to pick up randomly — you can provide the path from where to access, but due to some issues at runtime the software used to pick a wrong DLL or wrong library during runtime, compatibility issues happened, and you had to figure out why the application was not running. Since there was no version control system, artifacts were just copy-pasted into the folder, and with multiple artifacts in the folder the application used to randomly pick any one of them — the compatibility issues appeared more and more often. That is the dependency problem when it comes to libraries: your application picking up incorrect libraries or an incorrect artifact from a folder.

The textbook's precise definition: dependency hell occurs when an application depends upon one particular version of something, but is deployed with a different version, or with nothing at all. In early Windows, all DLLs lived unversioned in a system directory (windows\system32), and new versions simply overwrote old ones; in versions before Windows XP the COM class table was a singleton, so an application that needed a particular COM object was handed whichever version had been loaded first. It was impossible for different applications to depend on different versions of a DLL — or even to know which version they would get at runtime. The .NET framework resolved this with signed, versioned assemblies stored in a global assembly cache (the GAC) that distinguishes versions even with identical filenames; Linux used a naming convention appending integers to .so files in /usr/lib with soft links for the canonical system-wide version.

Worked example — DLL hell, step by step. Two applications on one Windows machine:

  1. Application A was built against mail.dll version 1 and works fine.
  2. Application B is installed; its installer copies mail.dll version 2 into the shared system32 folder, overwriting version 1 (no versioning was enforced).
  3. The user launches Application A. Windows resolves mail.dll from the shared folder and hands it version 2 — the wrong library.
  4. Application A crashes or misbehaves with a compatibility error: its code calls a function whose signature changed in version 2.

Final answer: Application A fails not because its own code changed, but because the runtime picked the wrong version of a shared library. Sense-check: every step follows from one root cause — unversioned libraries in a shared location — which is precisely the failure mode version control and versioned naming fix.

Real-world: DLL hell is a famous Windows development failure mode — the exact scenario of an application grabbing the wrong DLL from a shared directory.

8.2.5 Managing Libraries: Version Control and a lib Directory

The best way to manage libraries is to keep them in a version control system rather than just copying them somewhere. For example, you were on JRE 7, now there are changes and you want JRE 8 — such libraries rarely change, and that is fine. But if your application is dependent on a third-party vendor tool, or you have your own libraries that you want to use, those libraries can change. That library should also live in a version control system, and you should pick it from that version control system.

The simplest solution — and it works fine for any small project — is to create a lib directory under your code structure. You create a src folder that holds your source code files and test cases; at the same time you create one more folder, the lib folder, in which you can keep all build, runtime, and test-time dependency libraries. It is just a simple structure. Then give the libraries a naming convention that includes their version number, so you know exactly which version you are using. Say you use some third-party tool XYZ: name it XYZ 1.0; when it has been changed and you are using XYZ 1.2, update it with the 1.2 name — now you know in your folder which one is the correct one and which is actually getting used. (The textbook suggests refining the lib folder into three subdirectories — build, test, and run — mirroring the build-time, test-time, and runtime split of Section 8.2.3, and gives the same naming rule: check in nunit-2.5.5.dll, never just nunit.dll.)

The benefits: everything you need to build your application is in the version control system. Once you have a local checkout of your project repository, you can repeatedly build the same package that everyone else is building, because that folder you created gets copied onto your GitHub repository through the version control system. If you are a new member of the team, you just clone that repository to your local machine, and whatever the other team members are doing can be done on your side too, because you have copied the libraries as well — there would not be any compatibility issue. This is the textbook's repeatable builds constraint: if I check out the project and run the automated build, I get exactly the same binaries everyone else does — and the same binaries three months from now when I debug a problem reported by a user on an old version.

The problems: as your checked-in library repository grows with the implementation of a large-scale application, it becomes very difficult to figure out manually which of these libraries are being used in your application. And if your project must run with other projects on the same platform, manually managing transitive dependencies across projects repeatedly becomes painful. A transitive dependency is a dependency of a dependency: you use library X, and X itself needs library Y — so Y is transitively required by your application. With several projects on one platform, manually keeping track of "which Y version does each X need" across projects rapidly becomes painful. Even with version control and a lib folder, you are still maintaining those libraries manually — that is the difficult task.

8.2.6 Automating Dependency Management with Build Tools

The ultimate solution is to automate. Declare the libraries and use a tool like Maven, Ivy, or Gradle, depending on what technology and language your application uses. Declare those libraries in the form of a script and let the Maven or Gradle tool use it. You can either provide for the tool to download the libraries from internet repositories, or — and organizations generally prefer this — keep your own artifact repository, in which you manage your libraries, binaries, everything. In the script you write which particular library has to be picked from which repository. Instead of doing it manually, you are now writing it in code and using the build process tool. That settles the library side of dependency management.

The textbook adds two details worth knowing. First, these tools resolve dependencies transitively: Maven or Ivy will download the libraries you declare, work out what those libraries themselves need, and check the resulting dependency graph for inconsistencies — the classic one being two components requiring mutually incompatible versions of a common library. Second, the tools cache what they download on your local machine, so the first build on a new machine is slow but later builds are fast — and they prefer your organization's own artifact repository (open source options include Artifactory and Nexus) over the public internet, because an internal repository is what keeps builds repeatable and auditable (it also makes it easy to enforce legal constraints, such as avoiding GPL-licensed libraries in BSD-licensed software).

Pitfalls.

  • Treating components like libraries. Libraries are rarely updated; components change frequently. If you design the build process as if everything were a library, you will be surprised by frequent, breaking component releases. This is why the library/component distinction matters.
  • Building against one version, running another. The build-time/runtime split means your application can compile against a newer library than the one installed at runtime — a silent recipe for runtime failures. Pin and verify both.
  • A lib folder that outgrows its purpose. Versioned lib folders work for small projects, but as the folder grows you lose track of what is actually used, and manual transitive-dependency management across projects becomes a full-time chore — the cue to switch to a build tool.
  • Uncontrolled public downloads. If the build tool always pulls the latest from the internet, two developers can silently build different binaries. Prefer exact versions and an internal artifact repository.

Recap. A dependency is software your code needs to build or run; there are libraries (not controlled by you, rarely updated) and components (developed by your organization, frequently changed), and there are build-time and runtime dependencies. Unmanaged shared folders produce dependency hell — the runtime picking the wrong version. The fix progresses from version-controlled lib folders with versioned names, to automated declaration with Maven, Ivy, or Gradle and an artifact repository. The next section moves from libraries to components: how to build components in pipelines so the whole system stays releasable.

Real-world & domain. Dependency management is a live, everyday problem in modern software. JavaScript's npm ecosystem repeatedly hit "left-pad"-style disasters and supply-chain attacks, and Android's Gradle builds pin exact versions because transitive resolution changed behavior; the .NET GAC exists specifically to end DLL hell; and every serious organization runs Artifactory or Nexus as a company-internal "library warehouse." When a company reports a diamond dependency problem — two libraries needing different versions of a shared logging library — that is dependency hell under a different name, and it is the same problem the build tools in this section are designed to prevent.

8.3 Component Pipelines and the Integration Pipeline

Hook. If every component is built by its own pipeline, what holds the application together? A monolith recompiles everything on every change — so how do you structure the build so a change to the login page does not force the payment code to recompile?

8.3.1 Separating Code Bases to Escape the Monolith

For components, you need to make sure the components are separated from the entire code base — your modules have to be separated from the whole code base — and that server and client are kept as different code bases. In the e-grocery example you have front-end code and back-end code, and generally you keep a separate lifecycle for back-end and front-end: a separate back-end code base and a separate front-end code base.

Why? Because it takes too long to compile and link the code. If you keep everything together in one application, in one code base, that is nothing but monolithic architecture. With a monolith it is difficult to compile the entire code: if there is only a change in the back-end part, your whole code base is still at one place, so front-end and back-end everything gets compiled again, and then the code links — that takes more time. So keep the code bases separated: whenever there is a change in one code base, only that one is built, and libraries from the previous build of your other components can just be used to launch the application.

The textbook lists the signs that it is time to separate a codebase into components: part of the codebase needs to be deployed independently (a server or a rich client); you want to turn a monolith into a core plus plugins; a component provides an interface to another system; and — directly relevant here — it takes too long to compile and link the code, or too long to open the project, or the codebase is too large for a single team.

Intuition + analogy. A monolith is a single enormous kitchen where one chef cooks everything for every table; a change to the dessert menu means the whole kitchen is reorganized. Component pipelines are a restaurant with separate stations — the grill station, the dessert station, the bar — each with its own prep routine. When the bar changes its menu, only the bar restocks; the grill keeps serving the same steaks from yesterday's stock. The analogy breaks because, unlike food, software components can break each other through their interfaces — which is why the pipelines must be wired together (Section 8.3.3).

8.3.2 One Pipeline per Component

Can we split the system into several different pipelines? Yes — since we have several different components in the application, we can split the pipeline into several different pipelines. The build for each component, or set of components, should have its own pipeline to prove that it is fit for use or fit for release. That pipeline performs all the steps: compile the code if necessary; assemble one or more binaries that are capable of deployment to any environment; perform unit tests and other test suites — acceptance, capacity, integration, and so on — and support manual testing wherever appropriate. (This is exactly the textbook's list: compile the code if necessary, assemble one or more binaries capable of deployment to any environment, run unit tests, run acceptance tests, and support manual testing where appropriate — the goal being early feedback on the viability of each change.)

In the e-grocery application — login component, search component, cart component, payment component — each has its own pipeline, so that only those components where you made a change go through build and compile and all other processes. If we make a change to login, the other components do not get built: only login goes to build, assembly, unit test, and the other testing suites. Then, to launch the e-grocery application, you need an integration pipeline that integrates all the components together — it integrates all the artifacts of the components and launches the application.

Scope — do not over-split. The textbook is emphatic that you should not create a pipeline for every DLL or JAR. The simplest approach — a single pipeline for the entire application, triggered on every commit — scales surprisingly far, and has the advantage that it is easy to trace which line of code broke the build. Separate pipelines pay off only when parts of the application have different lifecycles, are built by different distributed teams, use different technologies, are shared across several projects, or are stable and rarely change — or when the full build is simply too slow. One pipeline is better than two; two better than three. Split when feedback gets too slow, not before.

8.3.3 The Integration Pipeline and Release Flow

The integration pipeline assembles the packages and performs the subsequent testing, to make sure the system testing happens. Here you will have smoke testing and acceptance testing (the system test), then the result gets deployed to a testing environment for further manual testing if at all needed, and finally it is released to the production environment.

Look at the two-component diagram: component A has its own pipeline — compile and unit test and functional test — and component B has its own pipeline. Whenever a pipeline executes, an artifact gets created: build means once you compile and build something, the artifact is created, and those artifacts are nothing but your binaries. Those artifacts have to be stored in an artifact repository — in the figure the artifacts from component A's pipeline are stored there, with a similar structure for component B. So managing components is done with automation, with continuous integration pipelines.

The textbook describes the same flow precisely: the integration pipeline takes as its starting point the binary output of each component. Its first stage composes the appropriate binaries into a deployable package; the second stage deploys the result to a production-like environment and runs smoke tests — quick checks that the assembled application actually starts and its basic functions work; then conventional acceptance tests run on the whole application; then deployment to testing environments; then release. This matches the deployment-pipeline shape of T2_5: commit stage (compile, unit tests, analysis) → acceptance stage → capacity/UAT → production.

The textbook adds two principles to remember about integration pipelines: fast feedback (trigger downstream pipelines as soon as binaries are created and unit tests pass — do not wait for acceptance tests) and visibility (when an integration build fails, you must be able to trace back which versions of which components contributed to it — a modern CI tool shows you this in seconds).

8.3.4 When Pipelines Trigger

Q: When will the component A pipeline trigger? A: The pipeline triggers whenever the code commits happen — whenever the code change happens, this pipeline will trigger. And whenever a merge happens — whenever the code change happens, that also triggers it. Exactly.

So the trigger is any commit, merge, or code change to that component's code base.

The same trigger rule, in the textbook's phrasing: each component has its own pipeline, triggered by changes in that component's source code or by changes to any upstream dependency; downstream pipelines are triggered by the upstream component passing its automated tests.

Pitfalls.

  • Waiting for the full pipeline before triggering dependents. Feedback slows down if the integration pipeline waits for acceptance tests before it pulls new binaries. Trigger as soon as the commit stage produces binaries.
  • Building a "green" component into a broken application. Not every green build of a component is actually good when combined with the others. Teams must be able to see which versions of their component ended up in a green integration pipeline — only those versions are really green.
  • Letting several components change between integration runs. If many components change between runs of the integration pipeline, the pipeline spends most of its time broken and it becomes hard to find which change broke the application. Keep the change rate controllable and trace the version of each contributing component.

Exam note: per-component pipelines and the integration pipeline, with triggers on commit and merge, are core concepts — be ready to describe what happens when a single component changes (only its pipeline runs) and why the integration pipeline then assembles all component artifacts.

Recap. Escape the monolith by separating code bases; give each component (or set of components) its own pipeline that proves it is fit for release; store the produced binaries in an artifact repository; and use an integration pipeline to assemble them, run smoke and acceptance tests, deploy to testing environments, and release. Pipelines trigger on any commit, merge, or code change. The next section asks the sharper question: when components depend on each other, which pipelines must run after which change — the dependency graph.

Real-world & domain. This is exactly how real CI/CD systems are organized. A company with separate front-end and back-end repos runs separate pipelines per repo (the practical meaning of "separate code bases"), a shared mobile app pipeline builds the app for iOS and Android, and a Jenkins/GitHub Actions workflow defines when each job triggers. GitLab, GitHub Actions, and Jenkins all support downstream triggering — the artifact repository (Nexus, Artifactory, GitHub Packages) is the shared shelf from which the integration pipeline picks component binaries — which is the structure the course demo will show with Jenkins, Maven, SonarQube, and Selenium.

8.4 Dependency Graphs and Pipeline Triggers

Hook. When one component changes, which other builds must run? Get this wrong and you either build nothing (and ship a broken app) or build everything (and waste hours). The answer comes from a picture: the dependency graph.

8.4.1 The Directed Dependency Graph

To manage dependencies you can use a dependency graph. A dependency graph is nothing but a directed graph, and it should be acyclic. Whenever there is a cycle in the graph, you have a circular dependency — a build ladder — which is discussed in Section 8.5.

A directed graph is a set of nodes connected by arrows that point one way; here the nodes are components and an arrow points from a component to the thing it depends on. A directed acyclic graph (DAG) is a directed graph with no loops — you can never follow arrows and return to where you started. If arrows could loop back, component A would need component B, which would need component A, and no build could ever start. The textbook's instruction is blunt: if you draw the dependencies between components, it should be a DAG; a cycle is a pathological dependency problem.

8.4.2 The Portfolio Management Example

Take the example of a portfolio management application (taken from the textbook). The components are: the framework component, the report engine, the settlement engine, and the pricing engine — these modules, or components, form the portfolio management application. The diagram's arrows show the dependencies: the report engine depends on the framework; the settlement engine also depends on the framework; the pricing engine depends on the framework; and at the same time the pricing engine is also dependent on the CDS pricing library, which is a third-party library. CDS here means credit default swap — a finance-domain pricing instrument.

Worked example — the portfolio management dependency graph.

The nodes and arrows:

and the portfolio management application sits at the right, depending on all three engines (report, settlement, pricing). The graph is acyclic: arrows flow from the framework and the CDS library leftward through the engines to the application, and never loop back.

Final answer: the report, settlement, and pricing engines all depend on the framework; the pricing engine additionally depends on the third-party CDS pricing library; the portfolio management application depends on all three engines. Sense-check: every engine can be built from the framework plus (for pricing) the CDS library, and the application can then be assembled from the engines — no node needs itself, so the graph is a valid DAG.

8.4.3 Q&A: A Framework Change

Q: Whenever there is a change in the framework module, which of all the components will undergo continuous integration — which pipelines will be triggered? A: Apart from the CDS library, all the pipelines will be triggered. Settlement is dependent on the framework, pricing is also dependent on the framework, report depends on the framework, and the portfolio management application is dependent on all of these — so all of them go through their pipelines. Only the CDS library is untouched.

The textbook's trigger rule is worth stating precisely: if a successful change is made to the framework (its own pipeline passes its tests), its immediate downstream dependencies are rebuilt — the reports engine, the pricing engine, and the settlement engine. If all three pass, the portfolio management application is rebuilt using the new versions of all three upstream components. If any of the three intermediate builds fails, the portfolio management application is not rebuilt, and the framework is treated as broken until fixed.

8.4.4 Q&A: A Third-Party Library Change

Q: If there is a change in the CDS library, which pipeline will trigger? A: The pipeline for the pricing engine should trigger, and then the portfolio management application should trigger — because the pricing engine depends on the CDS library directly, and the portfolio application depends on pricing.

The textbook describes the same scenario: the CDS pricing library is a third-party, binary dependency, so if the version in use is updated, the pricing engine is rebuilt against the new version and the current version of the framework; that in turn triggers a rebuild of the portfolio management application. One important subtlety: the trigger is not an "and" relationship. If only the reports engine's source changes, the portfolio application is rebuilt whether or not the pricing or settlement engines are rebuilt.

8.4.5 The Pipeline Dependency Graph

You can visualize this with a pipelining dependency graph: for the framework there is one pipeline; the pricing, settlement, and reports components each have their own pipeline.

Q: Why is there no pipeline for the CDS library? A: Because that is the third-party library — it is not getting managed or changed in the team.

Changes in third-party libraries arrive in the artifact repository; when the artifact for the report engine that is being accessed changes, that is nothing but a code change that happened to the report engine — you have to make modifications to the reporting engine because that particular library got changed.

The graph shows the triggers. If you change the framework, pricing, settlement, and reporting pipelines are triggered. If you only change the pricing engine, the framework and others will not be triggered — however, the portfolio management application will be triggered by accessing the particular binaries and libraries. Practically, you have to make a change in your Jenkins pipeline file if you are using Jenkins (you will get to know in subsequent sessions): in the script you write that, if there is any change, the downstream pipeline should be triggered automatically once the framework is changed — that has to be maintained.

8.4.6 Upstream and Downstream Visualization

How can one visualize which particular libraries, or which particular version of your application, a component is used with? There are two approaches. The upstream approach works right to left: you are looking for the particular version of the portfolio management application, 2.0.263, and asking which libraries or binaries were used to build this particular version — yes, for pricing we used this one, for settlement engine this one, for report engine this one, and then this is the one for framework. That is your upstream dependency.

The downstream approach goes left to right: given a particular version of the framework component, which other components got built using this particular version of the framework? A tick means that version is actively used; a cross means it is not getting used; and there are three different versions of the portfolio management application that were built on the 1.3.2394 version of the framework component. This visualization exists to maintain and manage the versions of your application in a production environment, and to see by using which particular libraries the application was built. Manually it is difficult to see, so with a graphical view it becomes easy to visualize the dependencies.

Worked example — the two views with real versions.

  • Upstream view (right to left). You pick portfolio management application version 2.0.263. Reading leftward along the arrows, you find the exact versions that went into it: pricing engine 1.0.217, settlement engine 2.0.11, reports engine 1.5.5, and framework 1.3.2396. (In the textbook's Figure 13.4, version 2.0.263 of the app is built from exactly these four versions.)
  • Downstream view (left to right). You pick framework version 1.3.2394. Reading rightward, you ask: which components and application versions were built on top of this framework? The answer: several component versions (pricing 1.0.217, settlement 2.0.11, reports 1.5.5) and three versions of the portfolio management application (2.0.260, 2.0.261, 2.0.262) — a tick marks each version actually used, a cross marks versions not used.

Final answer: upstream answers "what built this app version?", downstream answers "what was built on this component version?" Sense-check: both directions are just walking the same dependency graph — right-to-left for inputs, left-to-right for consumers.

Pitfalls.

  • Ignoring trigger maintenance. The trigger logic does not maintain itself: if you use Jenkins, a change to the framework only cascades because the pipeline files say so. Forgetting to update the script silently breaks the chain.
  • Treating third-party libraries as buildable. The CDS library has no pipeline because the team does not change it. Wasting a pipeline on a third-party artifact just produces noise — its changes arrive as new binaries in the artifact repository.
  • Forgetting the tracing requirement. If the integration build fails, you must be able to say which version of every component contributed to it. Without upstream/downstream tracing, you cannot answer "which change broke the build?" in minutes.
  • Falling for the "and" trap. The portfolio application rebuilds on any single changed upstream path — it is not a gate that requires all engines to change together.

Exam note: dependency graphs and pipeline triggers, with the portfolio management example, may appear as application-style questions — practice answering "a change to X triggers which pipelines?" for each node of the graph, and be ready to explain why the CDS library has no pipeline.

Recap. A dependency graph is a directed acyclic graph of components; in the portfolio management example, all engines depend on the framework and pricing also depends on the third-party CDS library. Changing the framework triggers everything except the CDS library; changing the CDS library triggers pricing and then the application; the upstream view traces an application version back to its component versions, and the downstream view traces a component version forward to what was built on it. The next section confronts what happens when the graph is not acyclic — circular dependencies and the build ladder.

Real-world & domain. This is the daily mental model of release engineers. In finance, portfolio and risk systems genuinely depend on CDS and other pricing libraries supplied by vendors, and the team has no pipeline for those vendor binaries — it consumes them from the artifact repository. Modern CI platforms encode exactly these trigger rules: Jenkins pipelines, GitHub Actions workflows, and GoCD fan-in/fan-out dependencies all implement the same graph-walking logic, and tools like Jenkins' "pipeline dependency graph" visualization are the industry incarnation of the upstream/downstream tables shown in the textbook.

8.5 Branching Components and Circular Dependencies

Hook. Can two components genuinely depend on each other and still be built? Common sense says no — A needs B, B needs A, and nothing can ever start. Yet real projects survive this situation. How? With a ladder.

8.5.1 Branching at the Component Level

You can create feature and subsequent branches at the component level as well. Say there is only a change in the report engine and the others are not changing. Since each component has a different code base, you create a new feature branch for the report engine — refer to it as 1.1 — and do the changes in that branch. Bug fixes for the previous version can be done in the 1.0 branch. Once the code is ready for 1.1, you merge it to the main line, and the base code becomes report engine 1.1. This is nothing but branch by abstraction. Because the code bases are separated for each and every component, you get the flexibility to create multiple branches in each component and manage their code base changes in those branches, rather than doing it for the entire application.

The textbook describes the same pattern — branch by release — with the reports engine: the team creates a branch for the 1.0 release and develops 1.1 on mainline. New features continue on mainline; downstream users keep consuming the binaries from the 1.0 branch and get bug fixes checked into that branch; when they are ready, they switch to 1.1. The textbook is honest about the trade-off: branch-by-release defers integration and so is second best for continuous integration — but because components are (or should be) loosely coupled, the risk of painful integration later is more controllable, which makes it a very useful strategy for managing complex component changes.

Intuition + analogy. Version numbers on a component are like versions of a phone app: users on the old major version keep receiving small bug-fix updates (1.0.1, 1.0.2) while the team develops the next major version (1.1) separately. When 1.1 is ready, everyone upgrades — the "merge to mainline." The analogy breaks because a phone app has one team and one app; components have many consumers who may not upgrade immediately, which is why bug fixes must continue on the old branch.

8.5.2 Circular Dependencies and the Build Ladder

If the directed dependency graph contains a cycle, then the application has a circular dependency. Take the example: component A and component B — component B is dependent on component A, and at the same time component A is also dependent on component B. In this cycle A depends on B and B depends on A:

This is nothing but a build ladder. In a real-time project, component A is at version 1.0.21; the next time you write component B, to run component B you are using the previous version of component A, and then to run component A you are using this version of component B. So each component runs against the other's earlier version — that is a circular dependency, and it shows the build ladder. This is how it gets managed in real-time projects. Frankly speaking, it is difficult to explain in a normal way — how can this be possible in a real-time project? But most of you will agree that you come across this situation: a circular dependency in your application — and this is the hack to use it.

The textbook explains why this works: the cycle looks like a fatal bootstrapping problem — to build A you need B, to build B you need A — but you never begin a project with circular dependencies; they creep in later. The build ladder works as long as there is a version of component A that you can use to build component B. Then you use the new version of B to build the new version of A. Step by step, each component climbs one rung of the ladder.

Worked example — the build ladder with real versions.

The textbook's Figure 13.8 shows the ladder. Component A is at version 1.0.21, component B at version 2.0.3. The team needs new features in both:

  1. Build B 2.0.3 → 2.0.4 using the existing version of A, 1.0.21 — B's build works because it links against A's previous release.
  2. Now that B 2.0.4 exists, build A 1.0.21 → 1.0.22 using B 2.0.4 — A's build works because it links against B's just-completed release.
  3. If needed, repeat: build B again against A 1.0.22, and so on.

Final answer: each component alternates building against the other's previous version — B 2.0.4 was built on A 1.0.21, then A 1.0.22 was built on B 2.0.4. Sense-check: at every single build step, the dependency being used already exists — the "bootstrap" problem never actually occurs because the ladder always steps onto the previous rung.

Keep in mind: if you are using any build tool to automate your build process, no tool comes with the facility of managing circular dependency. You need to hack with your tool — hack the configuration settings of the tool — and make it supportive for your application. Circular and transitive dependencies are not directly available in the build process; with any tool you are using, you need to change the configuration settings to make it happen.

The textbook adds a critical caveat: if each component triggers a build of its dependencies automatically, the two components would build forever because of the circularity. You must be cautious about how the parts of the build interact — the ladder only works with deliberate, manual ordering. And the textbook's bottom line is firm: at runtime there is no problem as long as both components are available together, but always try to get rid of circular dependencies; use the build ladder only as a temporary workaround until you can eliminate the problem.

Pitfalls.

  • Auto-triggering in a cycle. If B's pipeline automatically triggers A's and vice versa, the two build each other forever. The build ladder requires explicit, controlled ordering — this is exactly why tools need configuration hacks.
  • Treating the ladder as a permanent design. Circular dependencies creep in later and are not something to design for; the ladder is a workaround while you plan the refactor (for example, extracting the shared part into a third component).
  • Expecting the tool to do it for you. No build tool supports circular dependencies out of the box — "it should just work" is not an option.

Exam note: circular dependencies and branch by abstraction are named techniques to be able to explain — be ready to draw the cycle, describe the build ladder mechanism (each component builds against the other's earlier version), and state that no build tool handles circular dependencies natively.

Recap. Components can be branched individually (features on 1.1, bug fixes on 1.0 — branch by release), which component-level separation makes possible. When the dependency graph contains a cycle, the application has a circular dependency; the build ladder — each component building against the other's previous version — is the hack that keeps such projects building, though no build tool supports it natively and it should be eliminated, not preserved. The next section steps back from components to the build process itself: its steps and the landscape of build tools.

Real-world & domain. Circular dependencies are a known anti-pattern in real build systems; tools like Maven refuse to resolve a project graph with cycles at all, which is why teams "hack" them — often by temporarily publishing a snapshot of one component to the artifact repository so the other can build against it, exactly the ladder mechanism. In enterprise monoliths being migrated to microservices, circular domain references are one of the first things architects break apart — the "always try to get rid of circular dependencies" advice is what refactoring tools like dependency-analysis reports enforce in CI.

8.6 The Build Process and the Build-Tool Landscape

Hook. "It compiles on my machine" — so why does it break in CI? Because a build is not one magic command; it is a process with recognizable steps, and every technology has its own tool for running them. This section maps both.

8.6.1 The Steps of a Build Process

The build process has a core set of steps. First, compile the source code. Second, the build process should run and evaluate the unit tests. Third, the build process should process the existing resources — if you have configuration files or something similar, they should be processed by your build tool. Fourth, generate the artifacts — depending on the technology you used and what artifact you want to generate out of it, those artifacts should be generated.

More steps are often executed in a build process: perform the administrative dependencies analysis; analyze the code quality with static code analysis; run more test cases like functional tests and capacity tests; and archive the generated artifact and package it in a central repository, from which other pipelines can pick those particular artifacts. These are usually the steps of any build process.

The textbook's survey adds the underlying principle: all build tools share a common core — they let you model a dependency network of tasks. When you run the tool, it calculates how to reach the goal you specify by executing tasks in the correct order, running each task that your goal depends on exactly once. For example, running your tests requires compiling your code and your tests and setting up test data; compiling requires initializing the environment; the tool works out the order automatically and never runs a task twice, even when several tasks depend on it. A task then has two essential features: the thing it does, and the other things it depends on.

Worked example — a mini build network. To run tests, the build must do: init, compile source, compile tests, and set up test data. Init comes first (everything depends on it); source and test compilation depend on init but not on each other; test data setup is independent of both. The tool executes init once, then compile source, compile tests, and set up test data in any order, then run tests. Final answer: init → compile source + compile tests + set up test data → run tests, with init executed exactly once. Sense-check: no task runs before its dependencies, and none runs twice — which is precisely what a correct build must guarantee.

8.6.2 Build Tools by Technology

Depending on the technology, there are multiple tools available in the market. For Rails technology, organizations prefer to go with Rake. For .NET technology, people tend to use MS Build. For Java technology you have Ant, Maven, and Gradle. For C and C++, the build tool is SCons. If you are using Android and mobile development with Kotlin, the best combination is to go ahead and use the Gradle build tool. So depending on the technology in which your source code is being developed, subsequent build tools are available in the market respectively — you can opt for one.

The textbook fills in the landscape behind this list. Make and its variants were the standard build tools for many years — powerful, product-oriented, and essential when compile time is a significant cost — but complex rules, whitespace-significant syntax, and OS-specific shell dependence pushed developers away; many C/C++ developers now use SCons (its build files are written in Python, which makes it powerful and portable, with Windows support and parallelized builds out of the box). Ant emerged as the Java community's cross-platform answer: fully cross-platform, with tasks written in Java and an XML external DSL; it became the de facto standard Java build tool and is still widely supported by IDEs — but it is verbose, XML-heavy, and needs a great deal of boilerplate. MSBuild (the .NET descendant of Ant and NAnt) is tightly integrated with Visual Studio and understands how to build Visual Studio solutions and projects. Rake is the dominant Ruby build tool — an internal DSL in Ruby, meaning build files are plain Ruby code with all the power of a general-purpose language, able to act as either a product-oriented or a task-oriented tool. The newer generation — Buildr, Gradle, and Gantt — combine internal DSLs with serious dependency management and multiproject build support: Buildr as a drop-in Maven replacement built on Rake, and Gradle for those who prefer their DSL in Groovy (or Kotlin). The professor's summary holds for all of them: pick the tool that matches your technology.

Intuition + analogy. Build tools are like the checklists an airline crew runs before take-off — same steps every time (compile, test, package), executed in the right order, no step skipped, no step run twice. A task-oriented tool (Ant, MSBuild) runs a fixed list of tasks and keeps no memory between builds; a product-oriented tool (Make, SCons) looks at files and timestamps and skips work whose output is already up to date — the incremental build that can save hours on large C/C++ projects. Think of the difference as a cook who always reboils everything (task-oriented) versus one who checks whether the rice is still warm first (product-oriented).

8.6.3 What the Tutorial Sessions Cover

As per the syllabus, this course covers Maven and Gradle. In the tutorial session, the Jenkins demonstration will cover Maven, SonarQube, and Selenium: a continuous integration pipeline will be shown for a Maven project with the Maven build tool, then SonarQube, then Selenium test cases. The next session is purely focused on Git and GitHub, and after that comes a quiz. If time permits, the tutorial session instructor will discuss either Selenium or SonarQube so that the tutorial sessions cover as much as possible.

(SonarQube — not "SonarCube" — is the open source platform for continuous inspection of code quality; SonarQube and Selenium are tools used in the demo pipeline, not build tools proper.)

Pitfalls.

  • Compiling from the IDE and shipping from the command line. If the build works only in the IDE, it is not a build — every modern platform's build must run from the command line so CI can run it. The textbook's rule: once the project is bigger than one person or one executable, script the build.
  • Confusing build tools with integration tools. Maven/Gradle build; Jenkins is an integration system that runs the build tool on a schedule or trigger. The demo's pipeline (Maven → SonarQube → Selenium) is a pipeline of tools, orchestrated by Jenkins.
  • Letting the build grow unmaintained. Build scripts are living software: they must be designed, maintained, and exercised as regularly as the source code itself, or they fail exactly when you need them.

Exam note: build process steps and build tools per technology are syllabus topics, with Maven and Gradle in focus — be ready to list the core steps (compile, unit tests, process resources, generate artifacts, plus analysis and archiving) and match tools to technologies (Rake → Rails, MSBuild → .NET, Ant/Maven/Gradle → Java, SCons → C/C++, Gradle → Kotlin/Android).

Recap. Every build runs a recognizable sequence — compile, test, process resources, generate artifacts, optionally analyze and archive — and every technology has its own tool (Rake, MSBuild, Ant, Maven, Gradle, SCons). The course focuses on Maven and Gradle — seen in the Jenkins pipeline demo with SonarQube and Selenium. The next two sections go deep on those two tools, starting with Maven.

Real-world & domain. The tool-per-technology mapping is the reality of every build system in industry: Android shops run Gradle (with Kotlin DSL), .NET teams run MSBuild or Cake, Rails apps ship with Rake tasks, and C/C++ teams rely on SCons or CMake. The "build as a dependency network of tasks" idea underlies every one of them — and understanding it is what lets an engineer read any project's build files, whatever the tool.

8.7 Maven

Hook. How does a single file of XML replace an entire build script — compiling, testing, packaging, and publishing a Java project — while also resolving every library your code needs? That is Maven's promise: describe the project once in a pom.xml, and the tool does the rest.

8.7.1 What Maven Is and Who Owns It

Maven is a build tool. Every component generates one artifact with it — either Java or even zip, anything — but every module will create an artifact. Maven is also a dependency management tool. It handles the versioning and releases of your application. You can describe the project using Maven and produce Java docs or site information with it. Who owns Maven? The Apache Software Foundation owns this build tool. Real-world: Maven's official website is itself built using Maven, and the tool is completely open source — whenever you want to use Maven, it is freely available.

The textbook frames Maven as the tool that removed the boilerplate of Ant: it has a richer domain model and makes many assumptions about how a Java project is laid out — the principle of convention over configuration. So long as your project conforms to the structure Maven dictates, Maven performs almost any build, deploy, test, and release task with a single command. Its second headline feature is automated management of Java libraries and dependencies between projects — the pain point of large Java projects. (The textbook also notes the trade-off: if your project does not conform to Maven's assumptions, making Maven do what you want can be extremely hard — what the professor later calls Maven's rigidity.)

8.7.2 The Maven Project Structure

A Maven project has a standard structure. There is a source folder — src/main/java: java is the technology you are using, and if you use any other language, this java folder is replaced by that language's name. By default, Maven looks for this structure: the src, main, and java directories underneath your project. Then there is the target folder, the folder where Maven compiles all your source code: whatever gets compiled and whatever output gets generated is stored in the target directory. The pom.xml is the Maven-compiled source code, in a way: how to compile this source code is provided in the pom.xml, and it is also where you manage your dependencies — you mention there how to compile the source code and what artifact has to be generated from it. Maven supports different languages; it is not restricted to Java — it works with Groovy and other resources, just the folder structure changes to the language's name.

For unit testing, Maven searches for the folder structure src/test/java — again, if you have written in Java you write java, and other resources mention their names; to perform unit testing this folder structure has to be there. Everything gets compiled in the target directory — even your tests get run and validated in the target directory — and the package contains jar, war, or zip: the artifact generated from the source code lives in the target directory.

Worked example — a standard Maven project layout.

my-app/
      ├── pom.xml                  ← how to compile, what artifact, which dependencies
      ├── src/
      │   ├── main/
      │   │   └── java/            ← production source (replace java/ with the language name)
      │   │       └── com/bits/HelloWorld.java
      │   └── test/
      │       └── java/            ← unit test source
      │           └── com/bits/HelloWorldTest.java
      └── target/                  ← created by Maven: compiled classes, test results, the packaged artifact

Final answer: source lives under src/main/<lang>, tests under src/test/<lang>, the instructions live in pom.xml, and all compiled output and the final artifact land in target/. Sense-check: a teammate who clones this project can build it on any machine — the structure is the convention both Maven and every other developer expect.

8.7.3 How pom.xml Identifies a Project: Group ID, Artifact ID, Version

Maven uniquely identifies a project — that is why it is a management tool too — with the help of group ID, artifact ID, and version. (The textbook calls these three coordinates, sometimes abbreviated GAV, and notes that together with packaging they uniquely identify any Maven domain object — project, dependency, or plugin.)

The group ID is often the same as your package: for example com.bits or com.maven.training is nothing but a group ID. Generally the group ID is the business name or application name that you would like to see on a web address or on a mobile application. The artifact ID is the same as the name of your application: hello world, if that is the application; on-demand service, if that is one application; e-grocery is the application name. So if e-grocery is developed under com.bits, then com.bits becomes the group ID and e-grocery becomes the artifact ID. The version shows the version of the particular project.

The version formats are usually major, minor, and maintenance. Major means the tag you would like to keep for your project; if it is under maintenance you give it as a maintenance; if it is released you give it as a release. And a hyphen followed by SNAPSHOT is the version of a project that identifies it as under development — it is not majorly or minorly released, not under maintenance, just under development:

For example, 1.0-SNAPSHOT means this is the 1.0 version of the on-demand service application and it is under development. So these three identifiers together uniquely identify the project.

Intuition + analogy. The three coordinates are like a book's catalog entry: group ID is the publisher or imprint (e.g. com.bits), artifact ID is the title (e.g. e-grocery), and version is the edition (e.g. 1.0-SNAPSHOT meaning "draft of the first edition"). Just as no two books share publisher + title + edition, no two Maven objects share group + artifact + version — that uniqueness is what lets Maven store and fetch them by name alone. The analogy breaks because Maven coordinates are namespaces: the group ID must be globally unique to avoid collisions between different organizations, which is why it is usually written in reverse-domain form like com.bits.

The textbook adds what SNAPSHOT means in practice: append -SNAPSHOT to the version and mvn install stores the artifact under a timestamped folder (version-yyyymmdd-hhmmss-n), so consumers who declare only 1.0-SNAPSHOT always get the latest development build. Snapshots are convenient but make builds harder to reproduce — use them with care.

8.7.4 Inside pom.xml: Packaging, Dependencies, and Plugins

The pom.xml is the main file of Maven: you mention how your source code should compile, what the output from this particular component should be, and how to package that output. Packaging is how we want to distribute our application — whether you would like to give it as a jar or a war; these are nothing but the binaries out of your module. By default, if you do not mention anything under the packaging section of your script, Maven generates a jar — the default packaging is always jar.

You manage dependencies in the dependency section of the pom file: if you want to use a library, you need to know three things — the group ID of that dependency, its artifact ID, and the version you want to use. Plugins go in the plugin section of the pom.xml, where you mention what plugins you want. (The textbook's Maven section adds useful details: each dependency can carry a scopetest for test-only libraries, runtime for libraries not needed at compile time, provided for libraries the environment supplies at runtime, and compile (the default) for libraries needed at both compile and run time — and you can even declare version ranges like [1.0,2.0). The examples in the textbook are declared exactly the professor's way: commons-collections:commons-collections:jar:3.2.)

Q: (from the chat) Is the artifact only the binaries or libraries produced? A: When it comes to artifact, it is just not only binaries or libraries — artifact means any output that is getting generated. There could be one component which just generates a PDF; that becomes the artifact out of that module or component. So artifact is output — any output.

This is a vocabulary correction worth holding onto: artifact = any output of a build (JAR, WAR, ZIP, PDF, documentation, reports) — not only binaries or libraries.

The hello-world pom — a worked example. For learning purposes, a pom.xml was created with the group ID com.bits; the application name is hello world, so the artifact ID becomes hello world; this is 1.0-SNAPSHOT, which shows that it is under development. The model version is 4.0. The packaging is jar. Under the dependencies section there is a dependency: commons-lang3 — the first dependency is commons-lang3 — with version 3.8.1, and the group ID is org.apache.commons. From that particular address, that library is fetched; that is the dependency for the application, for this module. In the plugin section, the first plugin is the Maven compiler plugin, of version 3.7.0, to build this source code, and the configuration settings that you mentioned get processed in the target.

Worked example — reading the hello-world pom line by line.

<project>
        <modelVersion>4.0.0</modelVersion>        ← the POM schema version Maven must parse
        <groupId>com.bits</groupId>               ← group ID: the business/package name
        <artifactId>hello world</artifactId>      ← artifact ID: the application name
        <version>1.0-SNAPSHOT</version>           ← 1.0, still under development
        <packaging>jar</packaging>                ← the artifact will be a JAR
        <dependencies>
          <dependency>                            ← the application depends on...
            <groupId>org.apache.commons</groupId> ← ...the Apache Commons organization...
            <artifactId>commons-lang3</artifactId>← ...library commons-lang3...
            <version>3.8.1</version>              ← ...exactly version 3.8.1
          </dependency>
        </dependencies>
        <build>
          <plugins>
            <plugin>
              <artifactId>maven-compiler-plugin</artifactId>  ← plugin that compiles the source
              <version>3.7.0</version>
            </plugin>
          </plugins>
        </build>
      </project>

Final answer: this pom declares a project com.bits:hello world:1.0-SNAPSHOT packaged as a JAR, pulls the commons-lang3 3.8.1 library from the org.apache.commons group, and compiles with the Maven compiler plugin 3.7.0. Sense-check: running mvn package on this project compiles the sources, runs the tests, and produces a JAR in target/ with the declared library on the classpath — every section of the pom maps to one part of that job.

Important: automation is not done automatically. There has to be a code logic or a script logic written to automate the job — the automation happens by using this particular pom.xml file. The pom explains the project, explains the dependency, and describes how you would like to build it.

Pitfalls.

  • Assuming "automation" means "no configuration." Maven automates the build only because the pom.xml says how — a missing or sloppy pom means a wrong build.
  • Forgetting the exact versions. For repeatable builds you must pin exact versions of dependencies and plugins; the textbook's warning is that a default-configured Maven can self-update plugins and fail unpredictably — or worse, build differently on different machines.
  • Overwriting artifacts with every install. Without SNAPSHOT, every mvn install overwrites the artifact in the local repository; snapshot builds create timestamped versions so consumers always get the latest development state.
  • Renaming artifact IDs casually. Because coordinates uniquely identify artifacts in repositories, changing an artifact ID or group ID silently breaks every consumer that declared the old coordinates.

8.7.5 Maven Goals: clean, package, install, deploy

Maven has multiple goals; the most common ones in industry are these four.

  • mvn clean — just cleans the target directory: whatever was generated in the last build, whatever output was stored in the target directory, gets erased. That is the best practice.
  • mvn package — first cleans, then compiles, then creates the package mentioned in the packaging section of your pom.xml. The package is generated and stored in the output directory. It first runs the compile goal, then runs the unit tests, and generates the artifact or package as per the pom.xml.
  • mvn install — first runs the full package goal, then installs the package in the local repository. By default, the local repository on your system is the .m2 folder, which you can find under the user system folders once you have installed and started using Maven. When you run Maven install, by default the package will be pushed into your .m2 folder as well.
  • mvn deploy — runs the install goal first, then deploys the package to the corporate or remote repository that you mentioned. It is just similar to file sharing: it will deploy all the artifacts to that particular repository.

There are other goals too, but these are the ones the industry most commonly uses when working with Maven projects.

How the goals relate — one sentence each. clean erases the previous build's output from target/; package compiles, tests, and produces the packaged artifact in target/; install does everything package does and then copies the artifact into the local .m2 repository so other local projects can use it as a dependency; deploy does everything install does and then uploads the artifact to the remote or corporate repository where the whole organization (and other pipelines) can pull it. Each goal is the previous one plus one more step — a ladder from your disk to the organization's warehouse.

(One strict note: in standard Maven, clean is a separate lifecycle goal — mvn package on its own does not erase target/. The professor describes the combined intent; in practice teams run mvn clean package to get exactly that behavior, and mvn clean remains the best practice before any release build.)

8.7.6 Project Inheritance

Maven supports project inheritance: with the pom file you can inherit configurations, so you can maintain your component-level dependencies via Maven — group ID, version, project configuration, dependency, plugin configuration, and so on. How does it work? Under the project you mention the parent: which project is to be inherited by this particular module. In the parent section you can mention the Maven training parent — this is the group ID and this is the version that has to be inherited for this particular project; the project is the Maven training project, and the packaging is jar packaging. So you can inherit the configurations — you can implement project inheritance in Maven.

The textbook calls this a dependency refactoring: instead of declaring versions in every project, define them once in a parent project — wrapping the <dependencies> block in <dependencyManagement> — and child projects declare their dependencies with no version at all; the version comes from the parent. A shared parent pom is exactly how an organization keeps consistent library versions across dozens of projects.

Worked example — a child inheriting from the parent. The Maven training parent declares group ID com.maven.training, version 1.0, and packaging jar. A module's pom lists this parent:

<parent>
        <groupId>com.maven.training</groupId>
        <artifactId>maven-training-parent</artifactId>
        <version>1.0</version>
      </parent>

The child inherits the group ID and version (it can omit its own), any dependency management, and plugin configurations. Final answer: one parent definition, inherited by every module — change the version in one place and all modules move together. Sense-check: with ten modules all sharing commons-lang3 3.8.1 via the parent, a security fix updates one file instead of ten — exactly why inheritance is the standard pattern for component-level builds.

8.7.7 Multi-Module Projects

Maven also supports multi-module projects — it has first-class multi-module support. Maven projects create one primary artifact, and a parent pom is used to group those modules. In the example, you have Maven training; under that, you have source code and a pom.xml. Under that you have Maven training web, for which you also have source code and a pom.xml. This pom.xml is your parent pom: the parent pom has packaging of type pom, and the modules will be maven-training and maven-training-web. In this way you can manage multi-module projects — one project containing multiple modules. And that is exactly what we want: this is how Maven supports component-based design.

Intuition + analogy. A multi-module project is like a tool chest whose outer box (the parent pom) holds multiple drawers (the modules). The outer box has packaging type pom — it produces no artifact of its own; it only lists and coordinates the drawers. When you build the chest, every drawer is built in dependency order, each producing its own artifact. The analogy breaks because drawers can depend on other drawers — maven-training-web typically depends on maven-training — so the build order is not simply top-to-bottom but follows the module dependency graph.

The connection to Section 8.1 closes the loop: multi-module Maven projects are how Maven supports component-based design — one parent pom grouping the components of an application, each module with its own artifact and its own dependency declarations, all versioned and built together.

Exam note: Maven's pom.xml, group ID, artifact ID, version, goals, and multi-module projects are likely assessment material — be ready to state the three coordinates, the version format (major.minor.maintenance with -SNAPSHOT for development), what each goal does, and the two ways Maven organizes many components: project inheritance (parent-child poms) and multi-module projects (parent pom with packaging pom).

Recap. Maven, owned by the Apache Software Foundation, is a build and dependency management tool built on convention over configuration: a standard src/main, src/test, target structure; a pom.xml identifying the project by group ID, artifact ID, and version; packaging, dependencies, and plugins declared inside; goals clean, package, install, deploy moving the artifact from target/ to the .m2 local repository to the remote repository; and project inheritance plus multi-module projects as the mechanism for component-based builds. The next section contrasts this with its rival: Gradle.

Real-world & domain. Maven is the backbone of the Java ecosystem: almost every open source Java library is published to Maven Central as GAV coordinates, and the pom.xml you write today is consumed by the same coordinates that tools like Maven Central and GitHub Packages index. Enterprises run internal Nexus/Artifactory mirrors of Maven Central so builds are fast, repeatable, and auditable. The course demo itself shows the canonical Maven workflow: Jenkins triggers mvn clean package, SonarQube scans the code quality, and the resulting WAR deploys to Tomcat.

8.8 Gradle

Hook. What if your build tool could remember last time's work — compiling only what changed and reusing everything else? That is Gradle's headline idea, and it is why Kotlin and Android developers chose it over its older rival.

8.8.1 What Gradle Is

Gradle is an open source build automation tool. Its build scripts are written in a domain-specific language (DSL). It is high performance because it runs only the required tasks which have been changed — it does not run the entire code, just the tasks that have been changed. Gradle also has a build cache that helps reuse the task outputs from the previous run: with the build cache there is no need to run all the tasks — only the changed task runs, and other task outputs can be accessed from the build cache. Gradle has the ability to share the build cache within different machines.

It is based on a JVM foundation, so the prerequisite for working with Gradle is the Java Development Kit, similar to Maven. And it is not just limited to Java — you can use it for other languages. As mentioned, Kotlin and Gradle is the best combination that you generally find in an organization.

The textbook groups Gradle with the new generation of build tools (Buildr, Gradle, Gantt) that "feature internal DSLs for building software" while making complex challenges like dependency management and multiproject builds just as easy as the older tools. Where Ant and Maven write their DSL in XML (an external DSL you must learn separately), Gradle's DSL is internal: the build script is a real programming language (Groovy or Kotlin), so the script has the full power of a general-purpose language.

8.8.2 Tasks and the Directed Acyclic Graph

The core concept is creating tasks and dependencies between them. Gradle calculates a directed acyclic graph (DAG) to determine which tasks have to be executed in which order. The graph can be changed through custom tasks, more plugins, or other modifications of the existing dependencies. You can work with plugins to allow Gradle to work with other languages — Groovy, Kotlin, C++ — you just need to install those appropriate plugins.

This is the same dependency-network idea from Section 8.6, made the heart of the tool: the build is a set of tasks (compile, test, package, ...), each task depends on others, and Gradle resolves the DAG — remembering which tasks ran and which outputs are up to date — to decide what to run and in what order.

Worked example — the task graph. The core model is based on the task: a directed acyclic graph of tasks. In the example graph, task A is the root; tasks B and C are dependent on task A; tasks D and E are dependent on C; and task Z depends on D and E:

To execute task Z, the order is: first task A, then task C, then task D and task E, and then task Z — the graph tells you which task has to be executed in which order. (D and E can run in either order once C has completed.)

Worked example — executing the task graph, step by step.

Goal: run task Z. Dependencies: Z ← {D, E}; D ← {C}; E ← {C}; C ← {A}; B ← {A}.

  1. Task A runs first — it is the root, with no dependencies.
  2. Task C runs once A has completed (C depends on A). Task B could also run now — B only depends on A — but it is not needed for Z, so Gradle skips it.
  3. Tasks D and E run after C. They are independent of each other, so Gradle may run them in either order — or even in parallel if the build is configured for it.
  4. Task Z runs last, once D and E have both finished.

Final answer: the execution order for task Z is A → C → {D, E} → Z, with D and E interchangeable. Sense-check: every task in the chain runs exactly after its dependencies — a valid topological order of the DAG, which is precisely what Gradle computes for any task graph.

8.8.3 The Anatomy of a Task: Action, Input, Output

A task in Gradle is nothing but a module or component where you have code that performs something — the actions. The inputs are the values given to those actions, and the output is, after performing the action, the generated output. So every task has these three parts: action, input, and output.

This three-part anatomy is what makes incremental builds and the build cache possible: Gradle records the task's inputs and outputs; on the next run it compares them. If the inputs are unchanged and the outputs already exist, the task is up-to-date and Gradle skips it, reusing the previous output — locally, and even across machines when the build cache is shared. Inputs can be files (a source folder), property values, or even other tasks' outputs; outputs are usually files or directories the action produces.

Worked example — the compileJava task. In a typical Java build, the compileJava task has: inputs — the source files under src/main/java and the compiler options; action — invoke the Java compiler to translate the sources to bytecode; outputs — the build/classes/java/main directory. On the second run, if no source file or option changed, Gradle marks the task up-to-date and does not recompile — the compile step that Maven always repeats is simply skipped. Final answer: action + inputs + outputs is the complete anatomy of any Gradle task, and it is what enables skipping unchanged work. Sense-check: this exactly explains Gradle's performance claim — it runs only the changed tasks because unchanged tasks prove, from their recorded inputs and outputs, that they have nothing to do.

Pitfalls.

  • Declaring incomplete inputs. Gradle only skips tasks when it can see all the inputs. A task that reads files it never declares as inputs will silently reuse stale outputs — the classic "Gradle didn't see my file" bug.
  • Letting the DAG grow into a cycle. Gradle resolves a DAG; a circular task dependency (task A depends on B, B depends on A) fails the build — mirroring the circular-dependency warning of Section 8.5 at the task level.
  • Skipping the JDK prerequisite. Gradle is JVM-based: without the Java Development Kit installed, nothing runs — a common first-run failure for beginners.
  • Expecting plugins to exist for everything. Gradle supports other languages only through plugins (Groovy, Kotlin, C++); a missing plugin means unknown task types, so install the right plugin before declaring tasks.

Exam note: Gradle's tasks, the directed acyclic graph, and the build cache are key concepts — be ready to explain that Gradle runs only changed tasks (incremental execution), reuses previous task outputs from the build cache (shareable across machines), and computes the execution order from the DAG of task dependencies.

Recap. Gradle is an open source, JVM-based build tool whose scripts are an internal DSL (Groovy/Kotlin). Its core model is tasks — each with an action, inputs, and outputs — connected in a directed acyclic graph; Gradle computes the execution order from the DAG, runs only tasks whose inputs changed, and reuses outputs from a build cache that can be shared across machines. The next section puts Gradle head-to-head with Maven to answer the practical question: which one should you choose?

Real-world & domain. Gradle is the default build tool for Android — every Android Studio project is a Gradle project — and is the standard choice in Kotlin shops (the "Kotlin + Gradle" combination the professor called best). Its incremental build and build cache matter most in large, multi-module codebases and CI: companies like Netflix and LinkedIn have reported build-time savings of minutes per build by sharing a build cache across CI agents and developer machines.

8.9 Gradle versus Maven: How to Choose

Hook. Both tools build Java; both support multiple languages. So which one should a new project pick — and what does the answer say about the project itself? The choice comes down to a trade-off between rigidity and speed.

Both Gradle and Maven support Java, and both support multiple languages. When it comes to choosing which one to opt for, you need some analysis — this comparison helps you decide when to use Gradle and when to use Maven.

8.9.1 Flexibility

Gradle has flexibility on the conventions: you can write the script because it is a domain-specific language, you can follow your own naming convention, and you can plug and play with the configuration settings of Gradle — it is so flexible, user-friendly, and customized. Maven has no flexibility on the conventions: you have to use strictly what is mentioned — the pom.xml example shows that strict way of using it. It is rigid.

The textbook frames the same point as convention over configuration: Maven's rigidity is sometimes considered a feature — it forces teams (especially large or inexperienced ones) to structure projects uniformly — but deviating from Maven's assumptions is painful. Gradle's internal DSL means the build script is real code: custom tasks, custom naming, and custom logic are ordinary programming.

8.9.2 Performance and the Build Cache

In terms of performance, Gradle processes only the files that have been changed. Reusability by working with the build cache is supported — that helps ship the project faster, because you are reusing; you are not wasting your time compiling all the tasks, only those which have been changed. Maven processes the complete build; there is no build cache concept, so shipping is slow compared to Gradle.

8.9.3 User Experience and IDE Support

When it comes to user experience: Gradle's IDE support is evolving — for example, you can integrate easily with Gradle in Visual Studio, but for other IDEs it is ongoing — while its command-line way of working is modern. Maven's IDE support is mature, and its CLI solution is classic in comparison with Gradle.

So the summary: if you do not need the build cache, working with Maven is easy because it has strong IDE support. If you need to plug and play with the configuration settings — for example in terms of managing those circular dependencies — customization is easy in Gradle because it is not that rigid.

Dimension Gradle Maven
Build script Internal DSL (Groovy/Kotlin) — the script is code External DSL (XML) in pom.xml
Conventions Flexible — custom naming and logic allowed Rigid — convention over configuration enforced
Incremental builds Yes — runs only changed tasks No — processes the complete build
Build cache Yes — shareable across machines No such concept
IDE support Evolving (strong in Visual Studio, ongoing elsewhere) Mature
Command line Modern Classic
Best fit Customized builds, circular/transitive dependency hacking, Kotlin/Android Standard Java projects wanting strong IDE support and uniform structure

When to pick which: choose Maven when you want a uniform, predictable, IDE-friendly build for a conventional Java project and you do not need a build cache; choose Gradle when you need performance (incremental + cache), Kotlin/Android, or the freedom to customize the build — the rigidity of Maven is precisely what makes it the wrong tool when you must bend the conventions.

8.9.4 The .m2 Repository versus the Gradle Cache

A closing note on the repositories. mvn clean is required to delete the target directory: the target directory has the processed files, and it should be clean so that there are no compatibility issues. Gradle, by contrast, has the build cache, which lives in a repository. The .m2 repository of Maven does not have processed files or artifacts — it just has the package, so you can think of .m2 as a repository and it can be used for data sharing. In the Gradle repository you will have the processed artifacts, which can be used to package the overall application or overall output.

The distinction: .m2 holds finished, packaged artifacts (the JARs your project produces and the libraries it downloads) that other projects can consume; the Gradle cache holds intermediate processed outputs (compiled classes, task results) so that tasks never re-run work that is still valid. That is why mvn clean matters — the target/ directory accumulates processed files and stale artifacts, and a clean slate avoids compatibility issues between builds — while Gradle's cache is designed to be reused.

8.9.5 Q&A: Gradle, Maven, or Jenkins — Which Is Widely Used?

Q: Among Gradle, Maven, and Jenkins, which one is widely used? A: Gradle and Maven are the competitive ones — people generally opt for one of them. That is why a survey was conducted, and these tools were added to the syllabus after the comparison. Jenkins is your integration tool — an integration system tool. When this course was designed, Jenkins was purely open source, and it is the best tool to understand the core concept of how you configure and create a continuous integration pipeline. Nowadays even CircleCI has come up with an open source approach where you can do 3000 free builds per day.

This question also draws the line between two roles that beginners often merge: Gradle and Maven are build tools (they compile, test, and package), while Jenkins is an integration system (it observes your repository, triggers the build tool, and orchestrates the pipeline stages). They are not rivals of the same kind.

8.9.6 Q&A: Does Python Need a Build Tool?

Q: What about Python — which build tool does it use? A: You do not need a build tool for Python: Python is a self-interpreter language, and it has its own build process. In general, people opt for Gradle with Kotlin, and Maven is best suited for Java.

The reasoning behind the correction: Python runs directly from source through its interpreter, so there is no compile step to automate in the Maven/Gradle sense; its packaging needs are handled by Python's own ecosystem (for example, pip and setuptools), not by a general-purpose build tool. Gradle pairs best with Kotlin; Maven is best suited for Java.

Pitfalls.

  • Merging build tools with integration tools. "Gradle vs Jenkins" is a category error: Gradle and Maven compete with each other; Jenkins competes with CircleCI and other CI servers.
  • Reading "no build tool" as "no build process." Python has no compile-oriented build tool, but it still has a build/packaging process — the professor's point is that a self-interpreted language does not need a tool to compile or assemble bytecode.
  • Choosing Maven for a project that must bend conventions. Maven's rigidity saves you from chaos but punishes customization; if you know you will need custom logic or dependency hacking, Maven is the wrong bet from day one.
  • Treating the build cache as a safe place for stale state. The Gradle cache reuses outputs because inputs are tracked; an untracked input produces a silently stale build — the same pitfall as Section 8.8.

Exam note: the Gradle-versus-Maven comparison of flexibility, performance, and IDE support is likely exam material — be ready to state Gradle's wins (flexible DSL, incremental builds, build cache) and Maven's wins (rigid but uniform conventions, mature IDE support), and to separate build tools (Maven, Gradle) from integration tools (Jenkins, CircleCI).

Recap. Gradle offers flexible conventions, incremental builds, and a shareable build cache; Maven offers rigid, uniform conventions and mature IDE support; .m2 holds packaged artifacts while the Gradle cache holds reusable processed outputs. Gradle and Maven are the competitive build tools, Jenkins is the integration tool, and Python, as a self-interpreted language, needs no build tool at all. The final section closes the session with course logistics and exam guidance.

Real-world & domain. This comparison is made every day in real engineering choices: a 2020s startup on Kotlin/Android picks Gradle for build speed; an enterprise Java shop standardizes on Maven so hundreds of developers produce uniform builds; CI platforms (Jenkins, GitHub Actions, CircleCI) merely run whichever tool the project chose. The "3000 free builds per day" open source tier of CircleCI — and GitLab CI's comparable free tier — are why integration tools are effectively free to try, while the build tool decision remains the one that shapes a project's build experience for years.

8.10 Course Logistics and Exam Guidance

Hook. The build-tool material is the syllabus for today — but what exactly counts, when is the quiz, and what does the exam look like? This closing section pins down the assessment structure so you can plan.

8.10.1 Q&A: What the CI/CD Demo Will Show

Q: Build and deployment — where will we actually see that? A: Through the CI/CD pipeline: you will be able to see what build artifacts got generated, how unit testing happened, then the Selenium test, SonarQube, and deploying it to a Tomcat instance — that will be a part of the demo.

8.10.2 Q&A: References for the Sessions

Q: Please provide the reference chapter for the topics covered today and in the last two sessions — it is not mentioned in the presentation deck. A: If it is not there, I will definitely add those references. For today's session there is no reference deck, so I will add that and then upload it.

8.10.3 Q&A: PHP Build Approaches

Q: (a question about build tool approaches for PHP) A: I have not gone ahead with searching this kind of approach, but definitely I will let you know about this — I know the experts who work with PHP applications, so I can check with them and I will let you know.

(For your own context: PHP follows the Python-like pattern of a self-interpreted language — Composer manages libraries and autoloading, and tools like Phing (a port of Ant) or simple shell scripts handle build tasks. The professor promised to confirm this with PHP experts.)

8.10.4 Assessment Structure and Weightage

The evaluation components for the course are: quiz 10 marks, assignment 20 marks, mid-semester 30 marks, and comprehensive 40 marks. The assignment will be opened after the mid-semester examination.

Q: Can you confirm the weightage? A: Quiz 10 marks, assignment 20, mid-semester 30, and comprehensive 40.

Q: Are there makeup examinations? A: Yes — makeup is arranged for the mid-semester as well; there will be regular and makeup examinations.

8.10.5 Quiz Scope and Timing

Quiz 2 covers everything you have learned till today — apart from whatever was covered for quiz 1, that is the part of your quiz 2. The quiz will be before the mid-semester. The quiz time window is fixed: generally it is 24 hours — one quiz was made 42 hours — and it cannot be made flexible, because this is the new pedagogy that was to be executed. The assignment, in contrast, gets weeks of time.

Q: Please give more days for solving the quiz. A: I will not be able to do anything with respect to the quiz: even the earlier one was 24 hours and I made it 42 hours, but I will not be able to make it flexible. The assignment got around three to four weeks of time. For the quiz it will not happen — this is the new pedagogy, and it is out of my hand.

8.10.6 Mid-Semester Syllabus and the Comprehensive Exam

Q: What will be the syllabus for the mid-semester? A: It will be communicated over the Teams channel and on the portal by Tuesday: till today, whatever was covered is part of the syllabus, and probably there could be a few questions on continuous integration — that topic is covered in the next upcoming session (either continuous integration, or unit testing and Selenium automation testing), and that will be part of it.

Q: What is the timeline for the mid-semester exam? A: The upcoming contact session, which is on the 18th: whatever content we will be covering there will be part of your syllabus.

Q: Will the final semester cover from contact session one? A: Yes — the comprehensive examination is from contact session one till the last session: the whole course handout is the comprehensive examination.

8.10.7 Exam Format and Preparation

Q: Can you help us with the exam format? A: The ninth contact session will be kept smaller: we will spare some time to discuss the examination question pattern — we will walk through the last year's question paper and discuss what was expected out of that question.

Also worth knowing for planning: the course is not hands-on and not a code-development based course, so you do not need to worry about that; the latest course handout has been uploaded on Teams, and the class has been asked to use the handout shared there. And there is a lot to study, but it is easy if you go slowly and steadily.

Exam note: the evaluation structure is quiz 10 + assignment 20 + mid-semester 30 + comprehensive 40 marks, with the quiz before the mid-semester and the assignment after it. Quiz 2 covers everything learned till today (on top of quiz 1's scope) in a fixed time window (generally 24 hours, not extendable). The mid-semester syllabus is everything covered till today plus the upcoming continuous integration / unit testing and Selenium automation sessions (cutoff: the contact session on the 18th), and the comprehensive examination covers the whole course handout from contact session one onward.

Recap. The session closes the loop: build and deployment will be seen in the CI/CD demo (Maven build artifacts, unit tests, Selenium, SonarQube, Tomcat deployment); references will be uploaded; PHP build approaches are to be confirmed; and the assessment plan is quiz 10, assignment 20, mid-semester 30, comprehensive 40, with the mid-semester syllabus being everything covered till today plus the upcoming CI/testing sessions. With the course logistics settled, you now have the full picture of this session's syllabus material: component-based design, dependencies, pipelines, dependency graphs, circular dependencies, the build process, Maven, Gradle, and how to choose between them.

Exam Guidance Summary

All the assessment-relevant guidance from this session in one place:

  • Weightage. Quiz 10 marks, assignment 20 marks, mid-semester 30 marks, comprehensive 40 marks. The assignment opens after the mid-semester examination; the quiz comes before the mid-semester.
  • Quiz 2 scope. Everything covered till today, on top of the quiz 1 content.
  • Quiz timing is fixed. The quiz window is generally 24 hours (one quiz was extended to 42 hours); it cannot be made flexible — the professor is bound by the new pedagogy. The assignment gets around three to four weeks.
  • Mid-semester syllabus. Communicated by Tuesday on Teams and the portal: everything covered till today, plus the upcoming sessions on continuous integration / unit testing and Selenium automation testing. The contact session on the 18th is the cutoff — whatever it covers is in the syllabus.
  • Comprehensive examination. Covers the whole course handout, from contact session one to the last session.
  • Makeup examinations. Available for the mid-semester — regular and makeup examinations both exist.
  • Exam format. The ninth contact session walks through the last year's question paper and the expected answers, so the question pattern is discussed there.
  • Content focus. The build-tool material of this session — component-based design and the three releasability approaches, library versus component and build-time versus runtime dependencies, per-component pipelines and the integration pipeline, dependency graphs and pipeline triggers, upstream/downstream visualization, circular dependencies, the build process steps, Maven (pom.xml, group ID/artifact ID/version, goals, inheritance, multi-module projects), Gradle (tasks, DAG, build cache), and the Gradle-versus-Maven comparison — is the syllabus material for today, alongside the last two sessions' content.
  • Not hands-on. The course is not a code-development course; you do not need to worry about programming it yourself.

Key Industry Applications

  • Real-world: Components ship as DLLs on Windows, .so files on Unix, and JAR files in the Java world.
  • Real-world: Payment gateways evolve with delivery, net banking, UPI, and credit card features; e-commerce search depends on successful authentication — everyday examples of component dependencies.
  • Real-world: Feature toggling (flag variables) is how production teams hide unfinished functionality; branch by abstraction keeps the main baseline defect-free during large-scale changes.
  • Real-world: DLL hell was a classic Windows failure mode — the wrong library getting picked from a shared folder at runtime.
  • Real-world: Library management in practice means versioned names (XYZ 1.0, XYZ 1.2) in a lib folder under version control, or declared dependencies pulled from internet repositories like Maven Central or from an organization's own artifact repository.
  • Real-world: Tomcat is a classic runtime dependency for Maven-based web applications; Java apps depend on the JVM, Rails apps on the Rails framework, C apps on the C standard library.
  • Real-world: Per-component pipelines with an integration pipeline, artifacts stored in artifact repositories, and triggers on commit or merge are how real CI/CD systems are organized; Jenkins pipeline files encode the trigger logic.
  • Real-world: In finance, a CDS (credit default swap) pricing library is a third-party library a pricing engine depends on — the dependency graph example is taken from a real portfolio management system.
  • Real-world: Build tools map to technologies: Rake for Rails, MS Build for .NET, Ant/Maven/Gradle for Java, SCons for C/C++, and Gradle as the best combination for Kotlin and Android.
  • Real-world: Maven is owned by the Apache Software Foundation and its official site is built with Maven; the .m2 folder is the local repository Maven install pushes packages into.
  • Real-world: Gradle's DSL scripts, incremental task execution, and build cache are used in modern Kotlin/Android shops; CircleCI offers an open source tier with 3000 free builds per day, while Jenkins remains the reference open source integration tool.
  • Real-world: In the course demo, a CI/CD pipeline builds a Maven project, runs unit tests and Selenium tests, scans with SonarQube, and deploys to Tomcat.

ITD Lecture 8 notes · Automating the Build Process with Maven and Gradle

Introduction to Devops· postgraduate· 2026-08-14

Sections Breakdown

1Component-Based Design: Why and How

Components are the modules of an application, packaged per platform (DLL, .so, JAR); component-based design brings reuse, loose coupling (seed of microservices), and large-team collaboration, but components form dependencies and release branches, making manual assembly a release-delay problem solved by feature toggling, incremental changes, or branch by abstraction.

2Managing Application Dependencies

A dependency is software another piece of software needs to build or run; libraries are rarely-updated packages not controlled by the team, components are frequently-changed software developed in-house, and dependencies split into build-time and runtime; unmanaged shared folders cause dependency hell (wrong version picked at runtime), fixed by version-controlled lib directories with versioned names, and ultimately by automated declaration with Maven, Ivy, or Gradle plus an artifact repository.

3Component Pipelines and the Integration Pipeline

Components need separate code bases to escape the monolithic architecture where every change recompiles everything; each component or set of components gets its own pipeline (compile, assemble binaries, unit and other tests), artifacts are stored in an artifact repository, and an integration pipeline assembles them, runs smoke and acceptance tests, deploys to testing environments, and releases; pipelines trigger on any commit, merge, or code change.

4Dependency Graphs and Pipeline Triggers

A dependency graph is a directed acyclic graph of components; in the portfolio management example the report, settlement, and pricing engines all depend on the framework, and the pricing engine additionally depends on the third-party CDS pricing library, which has no pipeline because the team does not change it; a framework change triggers all pipelines except the CDS library, a CDS library change triggers pricing then the application, and upstream (right-to-left) versus downstream (left-to-right) views trace which versions were used to build an application version or which versions were built on a component version.

5Branching Components and Circular Dependencies

Components can be branched individually (new features on a 1.1 branch merged to mainline, bug fixes on the 1.0 branch — branch by release); a circular dependency is a cycle in the directed dependency graph (A depends on B and B depends on A), and the build ladder works around it because each component builds against the other's previous version, although no build tool supports circular dependencies natively and the configuration settings must be hacked.

6The Build Process and the Build-Tool Landscape

The build process runs a core sequence — compile source, run unit tests, process resources, generate artifacts — plus optional static analysis, more test suites, and archiving to a central repository; build tools model a dependency network of tasks and differ as task-oriented (Ant, MSBuild) or product-oriented (Make, SCons) with incremental builds; the technology-to-tool mapping is Rake for Rails, MSBuild for .NET, Ant/Maven/Gradle for Java, SCons for C/C++, and Gradle for Kotlin/Android, with the course demo pipeline covering Maven, SonarQube, and Selenium.

7Maven

Maven is an Apache Software Foundation open source build and dependency management tool: a project has a standard structure (src/main/java, src/test/java, target), is identified in pom.xml by group ID, artifact ID, and version (major.minor.maintenance with -SNAPSHOT for development versions), declares packaging, dependencies, and plugins, and is driven by the goals clean, package, install, and deploy; project inheritance and multi-module projects (parent pom with packaging type pom) support component-based design.

8Gradle

Gradle is an open source, JVM-based build automation tool whose build scripts are written in an internal domain-specific language; it is high performance because it runs only changed tasks, reuses task outputs from a build cache that can be shared across machines, and computes execution order from a directed acyclic graph of tasks; every task has three parts — action, inputs, and outputs.

9Gradle versus Maven: How to Choose

Gradle wins on flexibility (internal DSL, custom conventions), performance (runs only changed tasks), and the shareable build cache; Maven wins on uniform rigid conventions and mature IDE support; the .m2 repository holds packaged artifacts while the Gradle cache holds reusable processed outputs; Gradle and Maven are the competitive build tools while Jenkins is an integration system, and Python needs no build tool because it is a self-interpreter language.

10Course Logistics and Exam Guidance

Course logistics: quiz 10 marks, assignment 20, mid-semester 30, comprehensive 40; quiz 2 covers everything learned till today in a fixed (generally 24-hour, non-extendable) window; the mid-semester syllabus is everything covered till today plus the upcoming continuous integration and unit testing/Selenium sessions, with the contact session on the 18th as the cutoff; the comprehensive examination covers the whole course handout; the CI/CD demo will show build artifacts, unit tests, Selenium, SonarQube, and Tomcat deployment; the course is not hands-on.

11Exam Guidance Summary

All assessment-relevant guidance in one place: weightage (quiz 10, assignment 20, mid-semester 30, comprehensive 40), fixed quiz timing (generally 24 hours), mid-semester syllabus (everything till today plus CI/unit testing and Selenium sessions, cutoff on the 18th), comprehensive covering the whole course handout, makeup examinations available for mid-semester, and the exam format discussed in the ninth contact session.

12Key Industry Applications

Real-world applications of the session: DLL/.so/JAR packaging per platform, e-commerce component dependencies, feature toggling and branch by abstraction in production, DLL hell history, versioned library management and artifact repositories, Tomcat as a runtime dependency, per-component and integration pipelines in CI/CD, CDS pricing libraries in finance, build tools per technology, Maven's Apache ownership and .m2, Gradle in Kotlin/Android shops, and the course demo pipeline (Maven, Selenium, SonarQube, Tomcat).

Postgraduate students of software engineering and delivery

Exam Revision Notes

Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.

Component-Based Design: Why and How

Must-know: The three approaches to keep an application always releasable: feature toggling (hide unfinished functionality behind a flag), incremental small changes, and branch by abstraction (develop on a branch/abstraction, merge only when complete).

⚠️ Top pitfall: Forcing an infeasible small split (a login button without a password is not releasable), or leaving feature flags in place forever.

Self-check: Why does the payment gateway depend on the grocery item management module in the e-grocery app?

Connects to: Section 8.2, Section 8.3

Managing Application Dependencies

Must-know: Library dependency (package your team does not control, rarely updated) versus component dependency (developed by your team/organization, frequently changed); build-time dependency (present at compile/link, e.g. C headers) versus runtime dependency (present when the application runs, e.g. Tomcat, DLLs).

⚠️ Top pitfall: Treating components like libraries, or building against one version while running another.

Self-check: Why does a running Tomcat server count as a runtime dependency for a Maven-based web application?

Connects to: Section 8.1, Section 8.3, Section 8.7

Component Pipelines and the Integration Pipeline

Must-know: Each component has its own pipeline (compile, assemble binaries, unit tests, other test suites); a component pipeline triggers on any commit, merge, or code change to that component's code base; the integration pipeline assembles all component artifacts, runs smoke and acceptance (system) tests, deploys to a testing environment, and releases to production.

⚠️ Top pitfall: Over-splitting into a pipeline per JAR/DLL — keep one pipeline until feedback gets too slow.

Self-check: When will the component A pipeline trigger?

Connects to: Section 8.1, Section 8.4

Dependency Graphs and Pipeline Triggers

Must-know: In the portfolio management graph, all pipelines except the CDS library trigger when the framework changes; a CDS library change triggers only the pricing engine and then the portfolio management application; the CDS library has no pipeline because it is a third-party library not changed by the team.

⚠️ Top pitfall: Treating the portfolio rebuild as an 'and' condition on all engines, or forgetting to maintain trigger scripts such as Jenkins pipeline files.

Self-check: If the CDS library changes, which pipelines trigger and in what order?

Connects to: Section 8.3, Section 8.5

Branching Components and Circular Dependencies

Must-know: A circular dependency is a cycle in the dependency graph: A depends on B and B depends on A; the build ladder builds each component against the other's previous version (A 1.0.21 used to build B 2.0.4, then B 2.0.4 used to build A 1.0.22); no build tool manages circular dependencies out of the box — the tool configuration settings must be hacked.

⚠️ Top pitfall: Letting the two components auto-trigger each other's builds — they would build forever; the ladder needs deliberate ordering.

Self-check: How can component B be built when it depends on A, and A depends on B?

Connects to: Section 8.4, Section 8.6

The Build Process and the Build-Tool Landscape

Must-know: Core build steps: compile the source, run and evaluate unit tests, process resources (configuration files), and generate artifacts; extra steps include dependency analysis, static code analysis, functional/capacity tests, and archiving the artifact to a central repository. Tools by technology: Rake (Rails), MSBuild (.NET), Ant/Maven/Gradle (Java), SCons (C/C++), Gradle (Kotlin/Android).

⚠️ Top pitfall: Confusing build tools (Maven/Gradle) with integration tools (Jenkins), or shipping builds that only work in the IDE.

Self-check: Which build tool is preferred for Kotlin and Android development?

Connects to: Section 8.7, Section 8.8

Maven

Must-know: A project is uniquely identified by group ID, artifact ID, and version; the version format is major.minor.maintenance with -SNAPSHOT for development versions; mvn clean erases target/, mvn package compiles/tests/packages the artifact, mvn install adds it to the .m2 local repository, mvn deploy pushes it to the remote/corporate repository; multi-module projects use a parent pom with packaging type pom.

⚠️ Top pitfall: Believing automation is automatic — the pom.xml is the script logic; also forgetting to pin exact dependency and plugin versions, which breaks build repeatability.

Self-check: What does mvn install do beyond mvn package?

Connects to: Section 8.6, Section 8.8

Gradle

Must-know: Gradle runs only the tasks that have changed (incremental execution), reuses task outputs from the build cache (shareable between machines), and determines execution order from a directed acyclic graph of task dependencies; a task consists of an action, inputs, and outputs.

⚠️ Top pitfall: Declaring incomplete task inputs — Gradle will silently reuse stale outputs; or declaring a circular task dependency that makes the DAG invalid.

Self-check: Why can Gradle skip the compile task on a second build when nothing changed?

Connects to: Section 8.6, Section 8.7

Gradle versus Maven: How to Choose

Must-know: Gradle: flexible conventions (internal DSL), incremental execution, build cache shareable across machines, modern CLI; Maven: rigid conventions (convention over configuration), no build cache, mature IDE support, classic CLI. Jenkins is the integration tool, not a competitor of the build tools; Python is a self-interpreter language and needs no build tool.

⚠️ Top pitfall: Treating Jenkins as a competitor of Maven/Gradle — it is an integration system that runs the build tool.

Self-check: Why is Maven considered rigid while Gradle is flexible?

Connects to: Section 8.7, Section 8.8

Course Logistics and Exam Guidance

Must-know: Weightage: quiz 10, assignment 20, mid-semester 30, comprehensive 40 marks; the quiz comes before the mid-semester and the assignment after it; the quiz window is generally 24 hours and cannot be made flexible.

⚠️ Top pitfall: Expecting the quiz window to be extended — the professor cannot make it flexible (new pedagogy).

Self-check: What is the total weightage split of the four evaluation components?

Connects to: Section 8.1, Section 8.7

Exam Guidance Summary

Must-know: Quiz 10 + assignment 20 + mid-semester 30 + comprehensive 40 marks; quiz before mid-semester; quiz window generally 24 hours and not flexible.

Self-check: When will the exam question pattern be discussed?

Connects to: Section 8.10

Key Industry Applications

Must-know: Components ship as DLLs on Windows, .so on Unix, JARs in Java; DLL hell is the classic wrong-library-picked-at-runtime failure; artifact repositories (Maven Central, organization-owned) make builds repeatable.

Self-check: How is a component packaged on Windows, Unix, and Java respectively?

Connects to: Section 8.1, Section 8.2, Section 8.4, Section 8.7

Was this lecture useful?

Loading comments…
🤖

BitsNotes AI Assistant

Subject Notes Assistant

Configure AI Chat

Choose how to access the chatbot
Have your own API key?

Switch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.

🔑 Enter API key above to fetch live models from provider, or enter model name manually.
OpenAI-Compatible API Support

Choose any provider preset (Gemini, DeepSeek, Kimi, GLM, MiniMax, Qwen, OpenAI, Groq, Ollama, etc.) or enter a custom endpoint URL.

Security & Privacy First

Your API key is sent directly from your browser to your specified provider. BitsNotes servers never store or see your key.