Maven and Jenkins: Building, Deploying, and Testing a Java Application
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
- Maven as the build tool - 8.7 Maven (Lecture 8)
- The POM coordinates - 8.7.3 How pom.xml Identifies a Project: Group ID, Artifact ID, Version (Lecture 8)
- Maven goals - 8.7.5 Maven Goals: clean, package, install, deploy (Lecture 8)
- The .m2 repository - 8.9.4 The .m2 Repository versus the Gradle Cache (Lecture 8)
- The deployment architecture - 5.13 The Deployment Architecture (Lecture 5)
- Jenkins job configuration and triggers - 5.15 Jenkins: Job Configuration and Build Triggers (Lecture 5)
- Cron jobs and scheduling - 5.16 Cron Jobs and Scheduling (Lecture 5)
- Jenkins environment setup - 12.3 Setting Up a Jenkins Environment (Lecture 12)
- Jenkins pipelines and the Jenkinsfile - 12.4 Jenkins Pipelines and the Jenkinsfile (Lecture 12)
- Merge conflicts - 9.7 The Classic Merge Conflict (Lecture 9)
- Merging and undo operations - 7.10 Merging, Merge Conflicts, and Undoing Work (Lecture 7)
Maven and Jenkins: Building, Deploying, and Testing a Java Application
14.1 The Session Plan
14.1.1 What We Build Today
How does a piece of Java code written on a laptop become an application that runs on a server? This session answers that question by walking one complete CI/CD loop with two tools: Maven and Jenkins.
We create a very basic Java project, build it with Maven, deploy it with Jenkins, and test it with JUnit. That sequence — code, build, deploy, test — is the whole day's agenda, and it mirrors how real DevOps pipelines work.
Think of it like an assembly line. In a car factory, the body shell, the engine, and the wheels each arrive at a station that does one job, and the car only moves to the next station when the previous job is done. The pipeline we build today is the software version: the code is the raw material, Maven is the station that assembles it into a package, Jenkins is the conveyor belt that triggers each station automatically, and JUnit is the quality inspector at the end. Each station must pass before the next one starts.
Both Linux and Windows are fine for the basic deployment architecture we use. Git covers the code repository side, and Jenkins handles most of the CI/CD operations. Maven is the building piece: building an application can mean dependency building. If you have 10 or 20 projects together, or multiple files, and you want a lot of dependencies added, you need Maven for that. Nearly everything else stays similar to what was covered before.
14.1.2 Testing Tools Beyond JUnit
Testing today uses JUnit because the project is Java. But a DevOps engineer should know the wider testing landscape, and it splits into categories:
- Unit and hybrid testing: Karma and Jasmine are mainly for website testing and hybrid testing. Cucumber serves the same hybrid space, and it can also be used for mobile applications that rely on a hybrid architecture. Mockito was named for mock testing — this is the standard Java mocking framework, used to create fake versions of objects so a unit test can isolate one class from its collaborators. Selenium is mainly for automation testing — the suite of tools that drives a real web browser through your application and checks that pages behave the way users expect.
Q: Why does the recording sound like "Marketo" and "Selenium APM"? A: The words are easy to mishear, but the tools are Mockito and Selenium. Mockito is a Java library for mocking — creating fake objects that stand in for real ones during a test. Marketo is a marketing automation product and has nothing to do with testing. Likewise, Selenium is the browser automation suite; "APM" (application performance monitoring) is a different category of tool. If you see either name on a quiz about testing tools, Mockito and Selenium are the testing ones.
Real-world: these tools exist because every project type needs its own test strategy. A pure Android native app, a web app, and a hybrid mobile app all get tested differently.
14.1.3 Quality Tools: Linting
For quality measurement, we use lints. A lint (from the old Unix program lint that flagged suspicious code) is a tool that reads your source code and reports problems — style mistakes, unused variables, dangerous patterns — without running the program. JavaScript has JavaScript Lint and JSLint, Java has Java Lint, and Android and iOS each have their own lints. ESLint is the commonly used one for JavaScript, but every platform has its own lint tool.
14.1.4 Load, Security, and Monitoring Tools
LoadRunner and JMeter are used mainly for security testing and other purposes such as load and performance checks — JMeter, for example, lets you simulate hundreds of users hitting a server at once and see whether it stays responsive. Then there are analytics and monitoring tools that provide more insights; these are not covered in this session, but at least you should know they exist and what they do as a DevOps engineer. These are the basic things you need for a DevOps CI/CD process.
Exam note: The quiz for this session covers Maven and Jenkins only, but a DevOps engineer is expected to know about the wider toolbox: unit and hybrid testing tools (Karma, Jasmine, Cucumber, Mockito, Selenium), per-platform linters (ESLint and friends), and load, security, and monitoring tools (LoadRunner, JMeter, and analytics platforms). You do not need to operate them, but you need to know they exist and what job each one does.
14.1.5 Cron Jobs and Architecture Topics
Two more items sit on the agenda. Cron jobs schedule all our scripts and jobs — we use them in shell scripting and inside Jenkins. And solution architecture with network topology is part of your network architecture and deployment architecture knowledge; it is the environment your pipeline deploys into.
Recap: One loop drives the whole session — code in Git, build with Maven, deploy and schedule with Jenkins, test with JUnit — and the supporting cast of testing, linting, load, and monitoring tools is what a DevOps engineer must at least recognize. Next we look at the Java architecture itself, so we understand exactly what Maven builds.
14.2 Java Architecture: What Maven Actually Builds
14.2.1 From Java Source to Class File
Since the project today is Java, we look at the Java architecture so you understand the process. You don't need to know every detail, but you do need the process, because the POM file you create for your Maven application is built around it.
Every Java file, when it compiles, becomes a class file. A class is a kind of executable — it is not fully executable on its own, but via the Java interpreter we can execute it.
Think of it like translating a book. The compiler is the translator: it reads your Java source (written in a language humans can write) and produces a class file in bytecode — a compact, platform-neutral form that machines can read. The Java interpreter (the JVM) is the reader: it takes the translation and actually performs the instructions. Without the translator you have no translation; without the interpreter the translation just sits on disk. That is why the class file alone cannot run — it needs the Java interpreter to execute it.
14.2.2 The Parts Inside a Class File
The class has multiple parts:
- Method area — where all your functions are present.
- Heap memory — a kind of runtime memory where objects live while the program runs.
- Stack memory — used for our operations, typically local variables and method calls.
- Native stack — mainly for embedded applications. If it is an Android application, up to this point it is the Java layer, and below it comes the Android layer.
- Library for GC — the garbage collector, for clearing up memory that is no longer used.
- Just-in-time compiler — see below.
Scope: This is a big-picture tour, not a JVM certification. You do not need to memorize every memory area for this session — what matters is knowing that a compiled class file is a structured container (methods, objects, stacks, and a garbage collector to reclaim memory), because the POM file you write later configures the process that produces and packages these files, not the internals of the JVM.
14.2.3 The Just-in-Time Compiler
Java does not compile everything beforehand. This is mainly Spring Boot architecture, or Spring architecture: when the application is about to run, it compiles and starts running at that moment. Because the JIT compiler does the work at the point of execution, the efficiency is better for this approach. This is why a Java app can feel slow to start but fast afterward.
Think of it like a chef who prepares each dish only when an order arrives. The JIT (just-in-time) compiler watches the program as it runs; when it sees a method that is executed over and over, it compiles that hot method into fast native machine code on the spot. The first orders are slow because the chef is still learning the routine; once a dish is frequent, it is made from memory. That is exactly why a Java application can feel slow to start and then get faster: the first minutes include compilation work that later minutes do not.
14.2.4 The Java Build Pipeline
The full process of a Java application: you have Java code, the Java compiler compiles it into a class file that holds bytecode, then it gets converted into a jar or a war based on whatever packaging we do, and finally we run it in the operating system of our system — Windows, Linux, Mac, whatever it is.
This is exactly what we do today as part of our Maven build. All the steps: the Java file is already there, we compile that into a class, then to a jar, then we run the test files, then we run it and test everything.
Recap: Java source is translated to bytecode class files, executed by the interpreter, speeded up by the JIT compiler, and packaged as a jar or war for the target operating system — and Maven automates exactly this chain. With the build target understood, we next see the deployment architecture the packaged application is pushed into.
14.3 The Common Deployment Architecture
14.3.1 From IDE to Code Repository
Here is how applications normally get deployed. You write code in an IDE — the integrated development environment, the editor with built-in tools for writing, running, and debugging code. Tools like Eclipse, IntelliJ IDEA, Android Studio, and Xcode are all IDEs; the demo in this session uses Visual Studio Code for the source files. Once the code is written, you put it in a code repository, and for that we use Git and everything around it.
Think of it like a manuscript pipeline. The IDE is the writer's desk, the code repository is the publisher's archive, and the deployment step is the printing press. The desk is where the text takes shape, the archive is the single authoritative copy that every team member pulls from, and the press decides how many copies (packages) to print and for which market (platform). Writing on your desk alone never reaches readers — the archive and the press are what ship the work.
14.3.2 Two Deployment Paths
Once the code is in the repository, there are multiple ways to implement it:
- Direct deployment — deploy straight into a web server. For web applications we normally do that.
- Build operation — package the code into multiple executables and push them to a marketplace.
14.3.3 Packaging Formats on Every Platform
The packaging formats depend on the platform: Android APK, iOS IPA, web WAR file, Java JAR file. EXE fits a Windows application, DMG for Mac, PKG for Linux — whatever you are doing. Once packaged, you push it to whichever marketplace you have: the App Store or your own website, where it lands in the application layer.
| Platform | Package format | Where it ships |
|---|---|---|
| Android | APK | Google Play store |
| iOS | IPA | App Store |
| Web application | WAR | Company web server |
| Java application | JAR | Direct or via repository |
| Windows | EXE | Website or store |
| Mac | DMG | Website or App Store |
| Linux | PKG | Repository or website |
Real-world: this is why the same source code can end up as an APK on Google Play, an IPA on the App Store, and a WAR in a company's internal web server — one codebase, many packages.
14.3.4 The Layers of a Deployment
The deployment has distinct layers. There is the web layer, the application layer, and then the runtime, which could be in the cloud, along with all the databases and everything. This is the overall deployment architecture of DevOps.
Within it, the Git layer is the actual code layer, holding the services that run in the integration and DevOps layer. Everything is documented in detail, but nothing here is complex — you just need to know what is present in each layer.
Recap: Code is written in an IDE, stored in Git, then either deployed directly to a web server or packaged into platform-specific formats for a marketplace — with web, application, runtime, and data layers underneath. This is the environment your Maven and Jenkins pipeline feeds into, so next we meet the build tool that turns source into those packages.
14.4 Maven: The Build Tool That Changed the Game
14.4.1 Life Before Build Tools
Maven is a build tool, mainly for building. Before Ant, Maven, and the rest — and there are multiple build platforms: Ant, Maven, Gradle, Groovy — building was manual. With old Java, C, or C++, you compiled 10 to 20 files in one shot with a single command: GCC for C and C++, javac for Java. You had to list every file in one compilation command. Then, for a jar, you ran the jar command separately. Everything was manual, or you fell back on shell scripts. That was the olden-day concept.
Think of it like cooking without a recipe card. You could still cook — you had the ingredients and the stove — but every meal meant remembering the whole procedure by hand: which pot, which flame, which order, and how long. A build tool is the recipe card: it stores the procedure once, and the kitchen (your machine) follows it every time, on any stove. The old way worked, but it did not scale when the menu grew to twenty dishes.
14.4.2 Ant, and the Shrinking of Steps
Then these tools started coming in, and they reduced the issues. When Ant arrived, it was a kind of boon for developers. Maven is built over Ant — it is a smooth way of doing the Ant process. Ant had some crude things: where GCC and javac needed 10 steps, Ant needed 5, and Maven needs only 3. The tools kept shrinking the complexity of the build, the compilation, and everything else we do. They simplified each and every part of the process.
| Era | Typical steps to build and package |
|---|---|
| Manual (GCC / javac) | 10 — compile each file, link, create jar, run tests by hand |
| Ant | 5 — scripted steps in a build file, still configured per task |
| Maven | 3 — a single command driven by one POM file |
The pattern across the industry is the same: each generation of build tools removes steps the developer had to remember, until the build is one command repeated everywhere.
14.4.3 What Maven Gives You
Maven's value comes in four pieces:
- It makes the build process easy. That was the first thing — simplifying compilation and packaging.
- A uniform build system. Flutter uses a single application for all embedded systems; Maven is similar. One common build works across Windows, Mac, Linux, the cloud, and DevOps platforms — you do not implement different things for different layers.
- Quality. Maven supports everything: testing, linting operations, performance, and lots more. Only the deployment part needs Jenkins; even Jenkins integration can be done inside Maven. The POM file can grow exponentially, with all operations done in a single file. That is why it gives good quality.
- Better development practices. When you compile, Maven itself provides a lot of warnings on top of your compiler: this is not correct, that is not correct, try to clear this, make the file size smaller, break into multiple files, reduce the method size, reduce the number of lines, reduce the number of characters. It goes to that level, and for developers it is a boon — you can filter out a lot of issues before they ever reach production.
The Flutter analogy (professor's own). Flutter lets one codebase run on every device — Android, iOS, web — because it ships a single runtime for all of them. Maven is the Flutter of builds: one uniform build process that runs the same on Windows, Mac, Linux, and the cloud, so you never write a different build for each layer of your deployment. Where the analogy breaks: Flutter targets devices, while Maven targets build environments — but both remove "one version per platform" duplication.
Exam note: today's quiz covers only Maven and Jenkins, so the "why Maven exists" story and the POM details below are the core study targets.
14.4.4 The POM: Project Object Model
The POM — Project Object Model — is a manifest file. Every framework has a manifest; Maven's is the POM file. It declares each and everything about the project, and it is the heart of the application. To read one, you can go from bottom to top; that makes it easier to understand.
What the POM is: The POM is a single XML file named pom.xml that sits at the root of the project and declares everything Maven needs to know — the project's identity (who made it, what it is, which version), what it produces (jar or war), which third-party libraries it depends on, how it should be compiled, and where the finished artifact should go. It is called the heart of the application because every Maven decision — compile, test, package, install — is read out of this one file. The professor's tip: when reading an unfamiliar POM, scan from the bottom upward; the later entries are usually the concrete build details, which makes the upper summary entries easier to place.
Recap: Maven exists because manual building (list every file, run every command) did not scale — Ant halved the steps and Maven cut them further, adding a uniform cross-platform build, quality checks, and the POM as a single source of truth. Next we open a real POM and walk through every part of it.
14.5 Anatomy of a POM File
14.5.1 The Core Coordinates
The example POM is the minimal one given by the Apache Maven project itself. One important thing: the 4.0.0 model version should not be changed at all. This is the POM version everybody uses, and it is not going to change — it is frozen now. They are not going to upgrade it and we cannot even downgrade it. It stays static at 4.0.0.
Then come three things, like the package name, package ID, and version you put on any Java project:
- groupId — the company ID of whoever is doing the project. It could be a company or your own thing; normally we put
com.something.somethingstyle. - artifactId — the project name. Whatever project you are doing, whatever its purpose, that is the artifact ID.
- version — the version number. It could be a patch version, an upgrade version, or a new fully updated version. It has a three-digit structure, covered in the versioning section below.
The coordinates rule. Together, groupId, artifactId, and version — sometimes written GAV — act like a postal address for the project: the group tells you which organization it belongs to (com.yourcompany), the artifact tells you which project inside that organization (inventory-service), and the version tells you exactly which snapshot of it you mean (1.0.0). Maven also writes artifacts into its local repository under a folder path built from these three values, so two projects with the same coordinates would collide — every artifact in Maven's world must be uniquely identified by its GAV. Reference texts write the shorthand as groupId:artifactId:packaging:version, for example commons-collections:commons-collections:jar:3.2.
A minimal POM, exactly like the Apache starter example, looks like this:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>my-app</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
</project>
14.5.2 Packaging: Jar, War, EAR
The packaging part says whether we produce a jar file, a war file, or an EAR file — whichever executable we are going to create. This one declaration controls what kind of package the build makes. If you do not state it, jar is the default.
| Packaging | Stands for | Used for |
|---|---|---|
jar |
Java Archive | Standalone Java libraries and applications (the default) |
war |
Web Archive | Web applications deployed into a web server |
ear |
Enterprise Archive | Enterprise applications bundling several war/jar modules |
14.5.3 Dependencies, Parent, and Modules
- Dependencies: in a small project you may have none, but if you use third-party plugins you add them here. You cannot implement everything yourself. For example, a date framework not provided straightforwardly by Java, UI widgets and components — whatever you need, you add via the dependency tag.
- Parent and child: used for inheritance. There is a parent and a child; the child can inherit a lot of things from the parent, and the parent can also take something from the child. That is how inheritance happens, and that is why the parent tag exists. In a real company you typically have one parent POM that pins the versions of every library, and every project child inherits those versions instead of repeating them.
- Dependency management: this manages local dependencies and runtime dependencies — like a Docker container holding runtime dependencies. Compilation-time things go in one bucket, runtime things in another; when the pod is running, it reads the information it needs from here.
- Modules: similar to dependencies, but this is your own local code. A dependency is a third-party thing you import as a plugin; a module is a local project you already have, linked by file path when creating the jar.
Dependency vs module, in one sentence. A dependency is a library someone else wrote that Maven downloads for you; a module is a project your own team already wrote that Maven links in by path. If your team splits one big codebase into a dozen projects, those are modules of each other; the libraries you import from the internet are dependencies.
14.5.4 Properties and Build Settings
Properties specify which version of JDK or which tool version the build should use — for example, which Java compiler version. You state whatever you are trying to do here.
Build settings control which build number, how big or how low it should be, and which version range to support. If you have used Android or the App Store, you have seen "this application doesn't support this phone." That is the build in action: the minimum version for building and all these constraints.
14.5.5 Project Metadata: Developers vs Contributors
Project-specific fields include the name of the project, its description, the URL of your application, when it got created, what license it uses, which organization it belongs to, and who the developers are.
The difference between developer and contributor: the developer is the one who created the project. The contributor is the one who helps — someone comes and says "there is a bug, can we fix it?" and if you say yes, they fix it, contribute, and come and go. That is what the contributor field is for.
14.5.6 Environment, Distribution Management, and Profiles
Environment settings are internal: for Jenkins integration, sending mails, integrating Git, integrating other repositories that need to be built before ours runs, and plugins — code that can be directly compiled and used as a plugin.
Distribution management says where the artifact should go: the web store or your website — where it should be pushed.
Profiles are a role kind of thing: who can actually run all these operations. Some parts belong mainly to the development team, others to the builds and integrations team.
This is a full-fledged POM. There can be bigger POMs with other things, but this much covers real-time operations. There is also a "fat POM" with every tag available in Maven; you can look it up in the reference repository when a specific scenario needs something extra.
14.5.7 Scopes: Compile, Provided, Runtime, Test, System
Scope is not part of the POM body — you add it as part of your Maven command or dependency declaration. Scope says whether you are going to package, install, compile, or run:
- compile — default. It compiles here itself. If you say nothing, Maven picks the latest or some default version. The dependency is needed both to build the project and to run it.
- provided — similar to compile, but you state what version of JDK or what is needed; you provide your own things. The code compiles against it, but the runtime environment supplies it — for example the servlet API on a web server that already ships it.
- runtime — the code is not available with you. Once you deploy, you get the code there and run it then. Runtime waits for compilation until you deploy. The build does not need it to compile, only to run.
- test — only for the testing part. It never runs in production deployment; internally it runs for you. JUnit is the classic example: needed for the test phase, never shipped.
- system — for local things. You say "this is available here, get it from the local directory." If you put system and do not give the exact file path, it throws an error. The system path needs a hard-coded path to be provided.
Scope: These five scopes only apply where Maven's build model says they do. A test-scoped library is not bundled into your jar and never appears in production; a runtime-scoped library is present when the app runs but not on the compile classpath. Picking the wrong scope is a classic build problem: the code compiles on your machine but the deployed application crashes with ClassNotFoundException because the library was marked test instead of compile.
14.5.8 Optional Dependencies
Optional is an important flag. Suppose two different packages or repositories: one has some information, another has some other dependency. For example, a date library — in some scenarios you do not even need it; without it your project still runs. Then you can mark that dependency optional: even if that date repository fails to compile, your application still runs. If you do not mark it optional and it fails, you cannot build at all — that becomes a problem. There is a common pattern: a third repository only needs the first repository; the second repository is optional to it, so the third never needs to care about it — it just calls the first.
Worked example: the optional flag chain. Say repository A is a logging library, repository B is a date library that itself uses A, and repository C is your application that uses B but never touches A directly.
- With B declaring A as a normal (non-optional) dependency: every project that uses B is forced to download and build A, because Maven treats A as part of B's required dependency tree. If A is ever missing, C fails to build even though C never uses A.
- With B declaring A as optional: A travels with B's build but is not pushed on to C. C only needs B. If A's repository goes down, C still builds and runs, because C never asked for A.
That is the whole value of the flag: an optional dependency is used by the library itself but not forced on the library's consumers.
14.5.9 Student Questions: What Can Maven Package?
Q: Can an NPM package be built with Maven? A: Mostly we go for jar, war, and EAR. I am not saying an NPM package cannot be built — you could create a shell wrapper and try it, but I had not tried it. Mostly we use Maven for Java-based things. NPM is the Node Package Manager; Node already has a package manager, so why would we go to Maven? Java has no package manager, which is why we use Maven. The node package manager is mainly for node packages, and that itself can be used; you do not need Maven for it.
Pitfalls:
- Never touch
modelVersion— it is frozen at4.0.0; upgrading or downgrading it is not supported. - Forgetting that
jaris the default packaging: if you meant a war for your web app and do not say so, you get a jar. - Confusing
optionalwithscope: optional controls whether other projects inherit the dependency; scope controls when your build uses it. - Using the
systemscope without a hard-coded file path throws an error — it is the only scope that demands an exact path.
Recap: The POM is a recipe with an identity (GAV coordinates), a product (jar/war/ear packaging), ingredients (dependencies, modules, inheritance), build constraints (properties, settings, scopes), and distribution details — and the professor's rule of thumb is to read it from bottom to top. Next, we look at how the version numbers in those coordinates are chosen.
14.6 Semantic Versioning
14.6.1 Major, Minor, and Patch
Versioning has its own semantics: the major version, the minor version, and the patch. You must be very, very clear when giving versions — whether you are putting a small patch, a minor enhancement, or a major change — and give the numbers based on that.
A version number has three slots, written as major.minor.patch:
- Major (the leftmost number) — changes when the change is big enough that old users may need to adapt: new features with breaking changes, new architecture.
- Minor (the middle number) — changes for a small enhancement that adds something new without breaking anything that already works.
- Patch (the rightmost number) — changes for an immediate fix: a bug in production patched right away.
The three rules, each with a worked bump:
- A major change moves 1.0.0 to 2.0.0:
- A small enhancement moves 1.0.0 to 1.1.0:
- An immediate patch — a bug in production fixed right away — moves 1.0.0 to 1.0.1:
Worked example: reading a version bump. A customer is on version 1.3.0. Three releases arrive over a month:
- You fix a crash that only appeared with a specific printer driver → patch →
1.3.1. Smallest possible change, no new behavior. - You add a dark-mode theme without touching any existing feature → minor →
1.4.0. - You redesign the API so old integrations break → major →
2.0.0.
Final answers: 1.3.1, 1.4.0, 2.0.0. Sense-check: the crash fix changed the third digit, the new feature changed the second, the breaking redesign changed the first — each slot carries exactly one kind of change.
In the demo POM the version is 0.1.0: when asked "what is one?", the answer is the minor version — only small changes are planned, so the minor slot is incremented.
14.6.2 Why Versioning Discipline Matters
This matters because as a DevOps engineer you need to know all these things properly to actually release. If the versioning is wrong, it becomes a problem for you and for your customers: people will think it is a major version, they will download it, and they will say only a small thing is present. That kind of disappointment is exactly what semantic versioning prevents.
The wrong-version trap (professor's warning). Suppose you bump a tiny fix from 1.0.0 to 2.0.0 by mistake. Customers read the version number as a promise: "2.0.0 is a major release with major features." They download it, find one small bug fix, and feel cheated — and they lose trust in your next release. The version number is a contract between you and your users; the whole point of semantic versioning is that the number alone tells them how careful to be about upgrading.
Recap: Major breaks things and bumps the first slot, minor adds features and bumps the second, patch fixes bugs and bumps the third — and the discipline protects both you and your customers at release time. With versions understood, we move to the commands Maven itself offers to build, test, package, install, and clean.
14.7 Maven Commands and Plugins
14.7.1 The Core Commands
The major commands we normally use:
mvn compile— compiling.mvn test— testing.mvn package— the executable gets created, a jar or war.mvn install— does all these things in one shot: compiles, creates the package, tests it, and then copies the created jar or war to the local dependency directory, normally the hidden.M2directory. This is where the confusion between compile and install comes from.mvn clean— removes the target directory (the class files and jar). The same job can be done withrm -rfon Linux ordel /fon Windows.
The lifecycle idea. Maven commands are not isolated tasks; they are stages of one lifecycle that always runs in order: clean (if requested) → compile → test → package → install. Running mvn package does not skip testing — it runs compile, then test, then packaging automatically. The reference texts describe this same staged model as the commit stage of a deployment pipeline: one command, the full chain, and if any stage fails the build stops there.
14.7.2 Running and Checking Versions
To execute your application: java -jar <name>.jar. To check the Maven version: mvn --version or mvn -v. Only those two spellings work — a plain mvn version (no hyphen) does not work; Maven just tries to build and throws an error. The version output shows useful build information: the Maven version, the Maven home, the Java version, the encoding, the locale, the operating system, and the architecture it is building for.
Pitfall: mvn version is not a thing. Only mvn --version and mvn -v print the version; without the hyphen, Maven treats version as a build goal, looks for a project to build, and errors out. This one-character slip is a classic beginner stumble.
14.7.3 The Surefire Plugin
Two plugins matter. Surefire is the unit-testing tool mainly used by Maven itself. You create a JUnit test case and this plugin executes it; you barely touch it yourself. Surefire also generates the test report — how many tests ran, what did not run, what failed.
14.7.4 The Shade Plugin and Fat Jars
The shade plugin — shade means shaded — is the one you actually touch. It packages the class files into a jar, and its purpose is best seen with the classical example: you have a hello world program, one class, and you implement a date functionality, another class — two things that both get added into a single jar. If you break that jar open, there will be two different jars with all dependencies properly added. Now another jar tries to use your jar: it uses Java 1.8 while you use Java 11. Two different versions, a dependency problem. Shade corrects all your dependencies and renames your versions to be compatible with other things. Once you give out your shaded jar, you can be happy — you do not need to worry about compatibility issues. The shade jar takes care of it.
That is why the result is called a fat jar: each and every application creates its own jar, everything has its own version, and nothing conflicts with anyone else's. The demo uses the shade plugin at version 3.2.4 — the latest at the time — bound to the package phase, with the goal shade that creates the shaded jar, and a transformer configuration that says which class is the entry point.
Worked example: the version-conflict problem shade solves. Suppose you build a utility jar with a date helper, and your utility jar internally uses a library compiled for Java 1.8. A second project — a Java 11 application — wants to use your utility jar. If the date helper were a separate jar with its own versioning, the two jars could disagree about which library version to load: your jar expects 1.8, the application expects 11, and the JVM has one copy of the library but two incompatible expectations. The shade plugin merges every dependency's classes into one single jar, so your utility ships with its own private copies and version names adjusted to avoid clashing. Result: one self-contained fat jar that runs anywhere, no matter what versions the surrounding application uses. Sense-check: a fat jar that still needed external jars to agree with it would not have solved the conflict — the whole point is that shade removes the need for agreement.
14.7.5 Student Questions: Compile vs Install
Q: What is the difference between mvn compile and mvn install? A: Compile only compiles your source into class files. Install does everything in one shot: it compiles, creates the package, tests it, and then copies the jar or war into the local dependency directory, the hidden .M2 folder. If you are deploying a website, you deploy that entire path — your jar file along with all the dependencies that already sit there. So install is the complete lifecycle command, compile is just the first step of it. Several students confuse the two because both start a build; the extra part — copying the finished artifact into the local dependency directory — is exactly what turns install into the one-shot command.
Recap: One lifecycle runs clean, compile, test, package, install in order; Surefire runs your unit tests and writes the report; the shade plugin produces a fat jar so version conflicts disappear. Next we see how to make all of this fire on a schedule — cron jobs.
14.8 Cron Jobs: Scheduling Scripts and Builds
14.8.1 The Five Fields
A cron job is mainly for scheduling, and it is a very, very simple file with just five parameters:
- — minute, from 0 to 59. The 60th minute becomes the next hour.
- — hour, 24-hour clock. If I want 2:35, then 35 goes in the minute field and 2 PM means 14, so 14 goes in the hour field.
- — day of month, 1 to 31. January 15th is 15.
- — month, 1 to 12, where 1 means January. The pair "1 and 15" means January 15th.
- — day of week: Sunday, Monday, and so on. If you remove the day and month, you can put the week part directly — every Sunday it should run, or every Monday.
Think of it like setting a calendar reminder. You pick a time (minute and hour), a date or day (day of month, month), or a weekday (day of week) — and the system fires the job whenever the current time matches all five settings. The star means "any value": put in the minute field and the job fires every minute; put a concrete number and it fires only at that number. The five fields are the five knobs of one reminder.
14.8.2 Example Schedules
Two concrete schedules from the walkthrough:
2:35 PM every day:
Every minute (used for the Jenkins demo build so the timing is visible):
Worked examples: translating wall-clock times into cron.
- 2:35 PM every day. 2 PM on a 24-hour clock is 14, so the hour field is 14 and the minute field is 35: — the star day, month, and weekday fields say "any day, any month, any weekday."
- Every minute. All five fields are stars: — "any minute, any hour, any day, any month, any weekday," i.e., 1,440 runs a day.
- 12 PM noon every day. Noon is 12 on the 24-hour clock, minute 0: .
- 10:15 AM every day. Minute 15, hour 10: .
Sense-check: in every case the minute and hour fields match the wall-clock time, and the stars say the date parts are unrestricted.
The slides carry more examples: running at 12 PM noon every day, at 10:15 AM every day — all the different ways of running via cron. It is very, very simple: just five parameters, and you can play with all the numbers and test it yourself.
14.8.3 Wildcards and Non-Standard Extensions
There are some wildcards, but this part is not standard: sometimes they work, sometimes they do not. The standard expressions are the safe path; you can create everything manually. Some systems offer "at monthly" — automatically once every month — and "at hourly" — every hour once. These extensions exist on some operating systems and not on others, so always follow the standard. Today we also see cron used inside Jenkins.
Pitfalls:
- Minute range is 0–59, not 1–60: the 60th minute belongs to the next hour.
- Hours use a 24-hour clock: 2 PM is 14, never 2.
- The field is 1–12 with January as 1 — writing 13 for a month is invalid.
- Non-standard extensions like "at monthly" or "at hourly" exist on some systems only; a schedule that works on your laptop may fail on the build server, so stick to the five-field standard.
Recap: Cron schedules any script with five fields — minute, hour, day of month, month, day of week — and the stars mean "any." We use it both in shell scripting and inside Jenkins to fire builds on a timer. Next, the worked demo: building the actual hello-world project end to end.
14.9 Building Hello World: The Worked Demo
14.9.1 The Source Files and the POM
The demo starts with a very, very basic Java file: a hello world class and a greeter class. The main method calls the greet function. Two files total: one is the hello world entry point, one is the greeter. The task: build this Java file into a class file, convert it into a jar, execute it, test it, and install it.
For that we need a pom.xml — the manifest that declares everything. The demo POM keeps 4.0.0 untouched. The groupId is spring (an example project, so it was just left as-is), the artifactId is the project name gs-maven, and the packaging is jar. The version is 0.1.0 — the minor version, because no big changes are planned.
The properties section sets the Java compiler to 1.8. One property is mainly for the executable: the version number and company in the file name. The resulting executable will be named gs-maven-0.1.0.jar. The compiler property controls which version compiles your files.
The next part is the maven-shade-plugin 3.2.4, for generating the jar. The execution is bound to the package phase — the point where the build becomes a jar or war file. If no executable type is given, it defaults to jar; war must be specified explicitly. The goal is shade. The configuration names the package and main class: in the hello package there is a hello world Java application that needs converting into a jar — which jar, the shaded jar — and it has a Java 1.8 dependency. That is what we explain to Maven in the POM file.
Real-world: the same POM pattern — groupId, artifactId, version, packaging, compiler properties, and the shade plugin — appears in thousands of Java and Spring Boot projects in industry.
14.9.2 Step 1: Compile
First check the version: mvn --version prints Maven 3.8.6, the Maven home path, the Java version, encoding UTF-8, English India locale, Mac OS, and the architecture. mvn -v is the same thing. Running plain mvn version throws an error because it tries to build instead.
Then mvn compile: Maven scans for the project and finds it — the output echoes the organization ID, artifact ID, version, and the jar packaging we gave. With no resource directory it says "skipping". Because the code was already compiled once, it says "nothing to compile — all classes are up to date". So the demo deletes the target directory — the executable location — and compiles again. This time it actually compiles: "Compiling 2 sources". The two Java files are taken from the src folder and turned into class files under target/classes. Opening them shows special characters — a class file cannot be read as text; it is bytecode. The build also lists which classes got created and which input files were used.
The lesson (professor's warning): target is where your executable lands, and running Maven from inside the target directory (instead of the project root) throws an error because Maven cannot find the source folder. Maven finds your project by looking for pom.xml; inside target there is no POM and no src, so the build fails immediately. Always run Maven from the directory that contains the POM.
14.9.3 Step 2: Package
mvn package runs the same checks — compilation is up to date, no resources — then testing starts: Surefire tries to run a test case, but since there is no test case it says "no tests to run". Then the jar plugin builds the jar: the format comes from the artifact ID gs-maven, the version number, and the packaging, producing gs-maven-0.1.0.jar. After the original jar is created, the shade plugin runs and creates the shaded jar. The shaded jar replaces the original and gets renamed to the exact file name we gave. The result: a normal jar and a shaded jar both created, with the final name on the fat jar.
14.9.4 Step 3: Run
Execute with java -jar on the file in target: the output shows "hello world". The independent jar also exists — both get the same output. Since there is no test case, testing reports "no tests to run". The run part is complete.
14.9.5 Step 4: Install
mvn install is the interesting one — the fat jar versus normal jar concept. Without the shade part, you take your jar and put it in a directory where all your dependencies are present; that is what install does. The output shows the local path under the user's home — it is not putting the jar into a local folder of ours, it is going into the repository part: .M2/repository, where all the dependencies are already available. If we deploy a website, we deploy that entire path — the jar file along with the dependencies. With the shade part, that whole path is not needed; the single shaded jar can go directly. Shade is not third-party — it is Maven's own plugin — but if you are not using it, install is the path to follow.
The full lifecycle, traced end to end.
| Step | Command | What happens | Output observed |
|---|---|---|---|
| 0 | mvn --version |
Prints tool info | Maven 3.8.6, Java version, UTF-8, Mac OS |
| 1 | mvn compile |
Scans for POM, compiles sources | "Compiling 2 sources" → target/classes |
| 2 | mvn package |
Runs tests (none), builds jar, shades it | gs-maven-0.1.0.jar (normal + shaded) |
| 3 | java -jar |
Executes the shaded jar | Prints "hello world" |
| 4 | mvn install |
Copies artifact into .M2/repository |
Jar placed beside shared dependencies |
Sense-check: each step's output matches its command — compile produced class files, package produced the two jars, run produced the hello-world output, and install placed the artifact in the local repository.
14.9.6 The One-Shot Shell Script
Everything seen as separate commands is collected into maven.sh, with one more step: clean first. So the script cleans, compiles, packages, runs, tests, and installs — the full sequence in a single file. Running it shows the whole lifecycle: cleaning complete, compiling, class files ready, resources and jar built, the executable runs and prints "hello world", the shaded jar is created, tests skip because none exist, and the install step happens at the end.
This is the form mostly used for Jenkins scripting and deployments: either a shell file, or each and every command typed separately and waited on. Shell scripts are easier to manage and maintain, and they fail the entire build if one step fails.
Why one script beats six typed commands. A script is repeatable — the same sequence runs identically on every machine — and it is a single unit Jenkins can execute as one build step. Because the shell exits with a failure code when any command in the chain fails, the build stops at the first broken step instead of silently continuing with a broken artifact. That fail-fast behavior is exactly what a CI pipeline wants.
Recap: The hello-world demo walked the whole lifecycle — compile, package, run, install — with the shade plugin producing a fat jar, and collected everything into one maven.sh script that Jenkins will later run. Next we add real dependencies and a real test.
14.10 Adding Dependencies: Joda-Time and JUnit
14.10.1 The Updated POM
The next step keeps the same greet function but adds two more lines: getting the local time and printing the local time, using a plugin — org.joda.time.LocalTime — which is a third-party vendor for us. Now we have a dependency, and the POM changes slightly: everything stays the same (4.0.0, file name, JDK 1.8), but a dependencies block appears with two entries:
- Joda-Time — groupId
org.joda.time, artifactIdjoda-time, and the vendor's version number. These three are not ours; we take them from the vendor who provides the library. The demo did not quote the version number; Joda-Time's own releases live on the 2.x line (a stable release around this Maven version is 2.10.x), and you copy the coordinates — group, artifact, and version — exactly as the vendor publishes them. - JUnit — groupId
junit, artifactIdjunit, version 4.12, with scope test. Without a scope, the default is compile — used for compiling and building. With scope test, Maven understands this works for the testing part only, and Surefire uses the JUnit test case to build and run the tests.
Most real POM files look like this one: more dependencies, sometimes more properties, different source and target — but the basic structure is the same block.
14.10.2 The Test Case
The test is a very, very small one: it checks whether the hello string is present in the greeter method. Whenever the greeter is called, hello is there, so the test reports success. Nothing beyond that.
The test, line by line. A JUnit test class holds a @Test method that calls the greeter and then asserts the result contains "hello":
- Call the greeter — it returns its greeting string.
- Check the string — the assertion looks for the word
helloinside it. - Report — because the greeting always contains
hello, the test passes every time.
That is the entire test: one assertion against one method. Small as it is, it is exactly how Surefire reports it: one test case run, zero failures, zero errors, zero skipped.
14.10.3 Building and Testing with Dependencies
The demo removes the target directory (mvn clean or rm -rf/del /f) and runs mvn compile — two files compiled, the class folders look the same as before. The difference appears at the jar stage. mvn package runs the test: "Test case run one. Failure zero. Error zero. Skip zero" — which means it passed. If any of those counts were one, something had a problem; here it ran properly with no issue. Surefire completes the test, the jar file is ready. Then java -jar with the gs-maven jar in target prints the local time and hello world — the Joda package got bundled inside the jar, which is why the time appears. The install step runs last and is examined in detail in the next section.
Reading the Surefire verdict. The four counters are a health report:
| Counter | What it counts | Meaning when non-zero |
|---|---|---|
| Tests run | Tests executed | 1 here — the hello test |
| Failures | Assertions that failed | 0 — every assertion passed |
| Errors | Tests that crashed with an exception | 0 — nothing threw |
| Skipped | Tests deliberately not executed | 0 — everything ran |
Final answer: one test, zero failures, zero errors, zero skipped — the build passed. Sense-check: a broken assertion would have moved the failure counter to one and Maven would have stopped the build before the jar was created.
14.10.4 Student Questions: Source, Target, and the Docker Idea
Q: What do source and target mean? The source is the Java code I have and the target is the jar file — are they supposed to be the same Java version? A: Source means starting, target means ending: from this Java version to this Java version I can work. And they can be different — the fat jar I create is why. If source and target differ, it will not impact the build, because all dependencies live inside my jar itself. Whatever JDK or JRE your system has will not impact my jar, because the jar carries everything it needs.
The Docker idea (professor's analogy). If you know Docker, it is similar: the entire container has every dependency it needs — everything is available with me, myself. So I do not worry about the target server where it gets deployed, because the executable itself has all the elements I need. Where the analogy breaks: a Docker container isolates the whole environment (libraries, runtime, operating system level), while a fat jar only carries the Java libraries — the JVM itself still has to exist on the target. You can see the same idea on the Android Play Store: an app declares from which Android version to which version it works — that is source and target in action.
14.10.5 Why Maven Is Mostly a Java Tool
Q: Is Maven only for Java? A: Mostly we use Maven for Java projects because Java doesn't have a different dependency manager — Java is the old set of code. The newer ecosystems were born with their own managers: Android comes with Gradle, iOS comes with info.plist and CocoaPods. When Java came in, they did not think about these things, so it had nothing. C had GCC, but we had nothing like that for Java. So Maven is for Java-specific projects: not only Java, but Java, Spring, and Spring Boot — whatever we go for in that world.
Pitfalls:
- Forgetting the scope: a JUnit dependency without
ships the test library into production builds — declare it test so it never leaves the testing phase.test - Assuming source and target must match: they may differ safely only because the fat jar carries its dependencies; with a thin jar, the target server's Java version matters.
- Copying dependency coordinates by hand: the groupId, artifactId, and version must match the vendor's published values exactly, or Maven cannot find the artifact.
Recap: Two dependencies entered the picture — Joda-Time for local time and JUnit 4.12 with test scope for testing — and the build now runs a real test with a clean Surefire report. Next we open the jars themselves to see why the fat one needs nothing else.
14.11 Inside the Jar: Fat Jar vs Thin Jar
14.11.1 A Jar Is a Zip
A jar file is a kind of zip file. Everything is a zip file in the end: EXE, DMG, PKG, IPA, WAR, JAR, EAR. You can open any of them with an unzipping tool and look inside — which is exactly what the demo does to contrast the two jars.
Think of it like a gift box. A zip archive is just a box with labeled compartments — you can pack class files, images, metadata, even whole libraries inside. A jar is a box with a specific arrangement: the compiled classes, a manifest file that says which class to start from, and often a copy of the project's own POM. Opening the box is how you inspect what the build actually put inside.
14.11.2 Inside the Original Jar
Extracting the original jar shows two folders: the hello folder with greeter.class and helloworld.class — the basic classes — and the meta-information, META-INF, used mainly by Maven. Inside it, the manifest file holds: the manifest version, the created-by field, the JDK version used, and the main class — hello.helloworld. The main class is the important information: Maven reads it and executes hello.helloworld directly, and since that class depends on greeter, greeter runs automatically.
The Maven directory inside the jar contains two files: the company name we gave as the organization ID, then the artifact ID, and inside that, two things: pom.xml — the exact XML we created — and a property file with artifact, group, and version, the same values we gave in the POM. Because everything is present, the jar can run independently; if something more is needed, it can be downloaded via this pom file.
14.11.3 Inside the Shaded Fat Jar
The complete jar looks different from the start: a new directory appears — the org directory for Joda. The hello classes are there, META-INF has the manifest and a license (present because third-party plugins are used), and Joda's packages are inside. For Joda there is its own Maven directory: a property file (artifact, group, version) and a pom, and inside that pom all of Joda's own dependencies — everything it needs. The contributors and every detail came from the Joda repository. Other classes Joda uses are present too — chrono, convert — everything. The jar also holds the test artifacts: the test class, the surefire report showing what ran, what did not run, and any failures — created by Surefire itself, internally.
That is the speciality of the shaded fat jar: whatever dependency Joda has, its dependency, its dependency's dependency — all available here, so the jar runs independently without any problem. You can deploy it and keep quiet; everything is taken care of within the jar file itself. If you have multiple dependencies, multiple folders appear here and the jar grows exponentially. The original jar is smaller and needs the shared dependencies; the fat jar is bigger and needs nothing else.
Fat jar vs thin jar, side by side.
| Original (thin) jar | Shaded fat jar | |
|---|---|---|
| Contents | Only your classes + META-INF | Your classes + every dependency's classes |
| Extra metadata | Your pom.xml + property file | Dependency poms, licenses, surefire reports |
| Size | Small | Big — grows with each dependency |
| Runs alone? | Needs shared dependencies on the classpath | Yes — nothing else required |
| Deploy style | Deploy the jar plus the dependency path | Deploy one file |
Sense-check: the demo jar without dependencies printed only "hello world" and needed the dependency directory, while the shaded jar printed the local time straight away — the Joda classes were physically inside it.
14.11.4 Install, the .M2 Repository, and Thin Jars
Now the install destination: inside the internal user home there is a .M2 folder — Maven's own repository. The repository holds other repositories used by Maven, and our own package sits under an "org" path. Joda-Time and JUnit are all there, already downloaded. Because all these packages are present, we can keep our jar small — a thin jar, not a fat one. Install creates a very, very light jar and puts it in the dependency section where all dependencies already live. The dependencies were downloaded by Maven previously; the fat jar would take all of those folders and convert them into a single jar file, while the thin jar keeps them in the shared path.
Scope: The choice of fat or thin is a trade-off, not a rule. A fat jar simplifies deployment — one file, no classpath management — at the cost of size and duplicate libraries across many services. A thin jar stays small but requires the deployment environment to provide every dependency, which is exactly why companies keep a shared artifact repository and why CI machines warm their .M2 cache. The .M2 folder is a cache and local repository: it stores what you build and what you download, so builds do not hit the network every time.
14.11.5 Student Questions: What mvn clean Does NOT Delete
Q: Does mvn clean clean the .M2 directory — the repository? A: No. It deletes only the target directory — the class path and jar file that get created by our build. It will not delete the entire .M2, because .M2 holds the dependencies of all the other Maven projects as well: ASM, GlassFish, Commons Codec, Jakarta, Java X, Eclipse — these are not used by my project, other people's projects use them. If we cleared the whole .M2, other projects would fail. It is like a repository, you should not clear it. Also, everything gets downloaded into it — if you clear it, every build has to download again and you waste your network. It keeps a buffer so you do not download each and every time.
How simple Maven is (professor's comparison). Compare with .NET: dotnet has a lot of things to do. Maven does not have that much — only about 10 commands and 40 to 50 lines of configuration. It is not even 5%, not even 3% of your dotnet. The POM file is an XML file, similar to the XML you create when you build a UI in .NET. That is why Maven is very, very simple to learn.
Pitfalls:
- Running
mvn cleanexpecting it to clear.M2: it only removes the target directory; the repository is shared and must stay. - Judging a fat jar by its size: bigger is expected — it holds every dependency's classes, licenses, and reports.
- Assuming the thin jar can run anywhere: without the shared dependency path it fails with missing classes.
Recap: Every jar is a zip box; the thin jar holds only your classes and leans on the .M2 repository, while the shaded fat jar packs every dependency inside and needs nothing else. Now we leave the build side and meet Jenkins, the automation server that runs these builds for us.
14.12 Jenkins: Setup, Dashboard, and Console Output
14.12.1 Jenkins Is a Java Application
Jenkins is again a jar file — a Java application. The setup uses a shell script that calls Jenkins, telling it to listen on localhost: 127.0.0.1, the loopback address, which is what localhost is. The port given is 5050, so Jenkins runs on port 5050. This is a local Jenkins, not a server. Two extra things are in the script: the port and a localhost flag. Normally you do not even need port 5050 — the default Jenkins port is 8080, but another program was already running there, so a different port was needed. The local Git repository needs an extra flag because newer Jenkins versions do not allow the use of a local Git repository; it was added to fix a Git issue. Normally you just call Jenkins.
If you double-click the executable you can see it run, and internally it is running from a war file. Jenkins shows "Jenkins.war" when it starts. So the war file is the internal packaging, but the command used to run it is the jar command — Jenkins is executed as a jar.
What is localhost? 127.0.0.1 is the loopback address — a machine's own network address. "Listening on localhost" means the application accepts connections only from the same machine, which is perfect for a demo: your browser on the same computer reaches Jenkins at http://127.0.0.1:5050, and nothing outside can reach it. A real Jenkins server would bind to a public address instead.
14.12.2 Student Questions: Jar or War?
Q: The quiz says Jenkins runs from a war file — is that right? A: We have a quiz on this, and there is a confusion in it. We used jar only, not war. When I ran Jenkins I saw it as a jar. I will speak with the coordinator and correct the question if possible; otherwise go for jar. Don't go for war. The war thing comes from what Jenkins shows internally when it starts — it says "Jenkins.war" — but the execution is the jar command. Keep war in mind as the internal packaging, but write jar on the quiz. All the other quiz content is correct; only this part has a confusion, and it will be corrected.
Exam note: if the war-versus-jar question appears on the quiz, the answer is jar — Jenkins is executed with the jar command even though its internal packaging is war. The professor flagged the confusion with the coordinator for correction.
14.12.3 The Dashboard and Build Statuses
The dashboard shows the projects: a Flutter project that is failing, a Kubernetes project not built, and the Maven project, which is successful. The status symbols are weather based: if it is raining, the build has some problems or errors; not started has its own symbol; sunshine means successful.
Opening the Maven project shows all the statuses: last build, last failed build, successful build, failed build, unsuccessful, completed. A failed build means it failed; unsuccessful means the build was terminated abruptly. "Success stable" means everything is success — there can be some in-between failures, but the build still got success; stable means every run is success. Completed means that build got completed fully. The last build updates every now and then based on whatever process is running.
The timing list shows the cron job in action: builds at 55, 54, 53, 51, 48, 47, 46 — the big gap happened when the service stopped for two or three minutes. Apart from that, every 1 minute it ran. The cron job was set to run every minute. The workspace shows source, target, and test folders — everything from the Maven build is there.
Reading the weather symbols and status words.
| Term | Meaning |
|---|---|
| Rain (weather symbol) | The build has problems or errors |
| Sunshine | Build successful |
| Not-started symbol | Build has never run |
| Failed | The build failed |
| Unsuccessful | The build was terminated abruptly |
| Success stable | Every recent run succeeded |
| Completed | That build finished fully |
The weather icon is a dashboard-level summary; the status words are the per-build verdict. Stable is the strictest: it means every run in the window succeeded, not just the latest one.
14.12.4 Console Output and a Failed Build
The "Build now" button builds immediately: the last build was 199, clicking it starts build 200. Inside the build, the console output shows each and everything: who ran it, what command was executed, where it executed, and the status. The build runs mvn clean — deleting — then compiling, then packaging, then testing, then installing, and after install it executes the application, which is why the run comes last. The same shell script shown earlier is what actually runs, and every step happens every time.
A failed build is instructive too: opening the last failed build shows "Build failure" with the reason — the goal you specified requires a project to execute, but there is no POM. Please verify. The console spells out what the problem was and what caused it, giving the entire output so you can find the cause. The same output can be viewed as plain text. These are the basic operations in a Jenkins setup.
Trace: build 199 to build 200. Clicking "Build now" increments the run counter: the previous build was 199, the new one is 200. The console output records the full sequence as it happens:
mvn clean— deletes the old target directory.mvn compile— recompiles the sources.mvn package— rebuilds the jar and the shaded fat jar.mvn test— runs the JUnit test.mvn install— installs into.M2.java -jar— executes the application, so the run appears last.
Sense-check: the console output is the same lifecycle we ran by hand in section 14.9 — Jenkins simply replays the script every time a build is triggered. And the failed build shows the system at work: the error "the goal you specified requires a project to execute, but there is no POM" tells you exactly what was missing — the build ran somewhere without a POM — so you know where to look.
Recap: Jenkins is a Java application that runs on localhost at a chosen port, shows builds with weather symbols and status words, and replays the full Maven lifecycle with full console output on every trigger — including failures, which print their own cause. Next we configure what a project actually does.
14.13 Configuring a Jenkins Project
14.13.1 General Settings and Source Control
Jenkins provides a lot of configuration. The project has a description — the demo project uses "Demo project explains CI/CD" to say what the project is about before starting a new build. Each option has a help text you can click when you do not understand what it means; the help gives a full-fledged understanding of each setting.
Then: is it a GitHub project or not? If it is, you give the github.com URL and the name. If you have any parameters — remember compile-time and runtime scopes: a runtime build needs parameters, like the -D flags seen in the Jenkins start command — you configure them here, choosing what kind of parameter it is.
If it is a Git project, you give the repository name and credentials. If you have none, you add them; you give the password and everything, and Jenkins GitHub integration happens — Jenkins integrates to your Git, and both run together. That is the Git, Maven, and Jenkins integration.
Throttle builds lets you slow or speed builds. Concurrent builds: normally we do not enable it. If a build comes while one is running, it waits — the new build is queued, and the running one shows "waiting for the other build to complete". Executing multiple builds at once risks resource problems: some code gets compiled, some does not, all that confusion comes. There are more advanced options: how many times it should retry on failure, when it should wait — like at 12 o'clock you keep quiet for half an hour — block the build if another project is running or if somebody is downloading, or use a custom workspace.
Concurrent builds, like two cooks in one kitchen. Two builds in the same workspace would read and write the same source, target, and jar files at the same time — one build's compile can overwrite the other's classes mid-way, producing artifacts nobody can reproduce. That is why concurrent builds stay off by default: the second build waits in the queue instead of corrupting the first.
14.13.2 Build Triggers: The Five Ways
There are five types of triggers:
- Trigger builds remotely (server execution). You have a Jenkins client on your local system; you trigger a build to your remote system. We do this mainly for security: if somebody tries to run a shell script directly on the server, it throws an error saying "I am not enabled for running shell scripts." But from your local repository you can trigger a shell script, run a shell here, and execute it on the server. That is server execution.
- Build after other projects are built. You have a back-end project and a front-end project. The back end should run properly and get success before the front end runs, because the front end depends on the back end: without the API logic, testers testing the front end first would crash — the back-end API is not there. So you enable "build after other projects", give the project path, and choose the condition: trigger only if the build is stable (the normal choice) — even if it is unstable, you would still build (very, very rare), or even on failure you would not build. Normally we go only with the stable condition.
- Build periodically (cron). The demo runs every minute; you can change it to every hour. Jenkins shows the schedule: last run at 8:18, next run at 9:18, Indian Standard Time.
- GitHub hook trigger. When code comes into GitHub, GitHub intimates Jenkins: "Jenkins, I got some update — can you build it?" Then Jenkins automatically starts building.
- Poll SCM. This is the reverse: Jenkins goes and verifies GitHub. You schedule it, for example every five minutes — it checks every five minutes once, and if a new change has come, it runs.
When to pick which trigger. Consider a team with a front-end app that depends on a back-end API, and a separate team that commits often.
- Back end changes → front end must rebuild: use trigger 2 with the stable condition. The front-end build waits for a successful back-end build, so testers never run against a missing API.
- A demo that must visibly fire on a schedule: use trigger 3 — the demo runs every minute purely to show the mechanics.
- Production efficiency: use trigger 4 (GitHub hook). GitHub phones Jenkins the moment a commit lands, and the build starts right away — no waiting, no wasted runs.
- GitHub that cannot phone out (no webhook support): use trigger 5 (poll SCM) — Jenkins checks every five minutes and builds only when something changed.
Sense-check: in every case the trigger picks builds when the work happens except the periodic one, which builds on a timer whether work happened or not — which is exactly why the hook is the recommended efficient way.
The recommended, efficient way is the GitHub hook (or polling): build only when a new change arrives. The periodic trigger wastes resources by building on a timer — the demo kept it only for testing, to show all the mechanics.
Exam note: know the five trigger types — remote trigger, build after other projects, build periodically, GitHub hook trigger, poll SCM — and why the hook is the efficient one: it builds only when code actually changes, while the periodic trigger burns resources on a fixed timer.
14.13.3 Build Environment Options
The build environment options are mainly for security and failure strategy:
- Delete the entire workspace before build — do not use this at all. Your code gets totally destroyed and you will not even get it back. If you add it by mistake, it becomes a bigger problem — the entire codebase is gone.
- Secret text — for encryption and decryption of secrets.
- Timestamps — if you want them on the console output.
- Build lock — lock builds and ask what kind of termination: absolute means after a fixed deadline, for example five minutes, end it; elastic takes the average of everything — if a build normally succeeds in two minutes and this one takes more than three or four, it knows it is likely stuck; no activity means if nothing is happening, end it.
The delete-workspace trap (professor's warning). "Delete the entire workspace before build" removes the whole code area before each build — the source, the target, everything — and it is not recoverable from Jenkins. The professor keeps this option permanently unchecked: one accidental click and the entire codebase is gone. Never enable it, and read every environment checkbox before saving.
14.13.4 Build Steps
There are multiple ways to execute a single task. The demo used a shell script — shell scripts are mainly for Linux, Unix, and Mac. Other ways: batch files (.bat) for Windows applications, Ant scripts, Gradle, and Maven targets.
Why not Maven targets here? Because each Maven target needs its own build step: mvn compile, then mvn install, then mvn test — multiple lines, multiple build steps. The shell script does the same thing in one step, and if something fails, it fails the entire build. It is the easiest, shortest, most optimized way for this scenario — your scenario could differ, and with multiple things you would use Maven-level targets, giving the Maven version, the goal (scope or package), and the POM file.
This is the main reason the POM file lives in a different place and the build runs in a different place: the Jenkins server is in a less secure area — it is just a deployment server without the code — and anyone there could read your POM file and see what you are deploying. So the code stays in one place, the build happens in another, and only the shell file goes to Jenkins — nobody can read what is there. You can also add multiple build steps, one after another: first you complete this, then that, then that.
Why the shell script wins here. Three Maven targets mean three separate build-step entries — compile, install, test — each configured individually. One shell script with all commands inside is a single build step that runs the whole chain and aborts everything on the first failure. Same result, less configuration, and a single file to review — for this scenario, the script is the shortest, most optimized path.
14.13.5 Post-Build Actions
Once the build is complete, post operations run: send a mail, publish the JUnit results, archive an executable, build the next project, download the result, release release notes — a lot of things, and everything can be automated. If you enable email, you give the recipients and everything. The Git publisher says where to publish, only on build success, which branch — all configurable. It reduces your load: you enable once, set all the configuration, sit quietly, and enjoy the life — it does everything for you. When the configuration is complete, apply it and save it.
Recap: A Jenkins project is configured in four blocks — general settings and source control, build triggers (five kinds), build environment options, and build steps — plus post-build actions that automate the follow-up. Next we look at the admin side: plugins, tools, agents, and upgrades.
14.14 Managing Jenkins
14.14.1 The Plugin Manager
How did Jenkins get its Maven dependency, Gradle dependency, and email dependency? Jenkins does not have all dependencies built in — that is the Manage Jenkins part, with the plugin manager. Opening the plugin manager shows installed plugins: Ant, Apache, and more. The available tab shows everything: Git server, Pipelines, JavaDoc, Docker, Kubernetes — whatever tool you want, whatever Jenkins supports, it is present. You download and install it, and then it becomes available in your configuration.
Think of it like an app store for Jenkins. Jenkins ships as a small core; everything extra — Git integration, Docker support, email, cloud agents — arrives as a plugin you install from the marketplace and use right away. That is why Jenkins interoperates with so many tools: each integration is a downloadable extension rather than a rewrite.
14.14.2 Global Tool Configuration
After downloading a plugin, you need to configure it. The demo got confused looking for Maven — it did not show in one list, but it is in the Global Tool Configuration. There you give the local Maven path if you already have Maven, or choose "install automatically" — Jenkins downloads it from the Apache server, you pick the version you want, and it builds your application with that version.
Pitfall: after installing the Maven plugin, Maven is not magically ready. The plugin adds the capability; the Global Tool Configuration still needs a Maven installation — either a path to a Maven you already have, or an automatic download of a chosen version. This is exactly the "M3 not configured" failure seen later when a pipeline asked for a Maven tool that had no installation behind it.
14.14.3 Nodes and Clouds
Runner agents attach to Jenkins in Manage Nodes and Clouds. There is a built-in node — the local node. To configure your own, you create a new node, which is a server, or a cloud: you download whichever cloud you want, configure the URL and everything, and then use it. From there you can do remote node monitoring. In the demo, an Amazon EC2 cloud plugin was being installed just to show how it looks — it asks for your instance IP and the S3 instance, whatever you have, and then you configure it. Everything is UI — you do not need to worry, everything is available in Jenkins and it is very straightforward.
Nodes and clouds, in one paragraph. A node is a machine that executes Jenkins builds — the built-in local node runs jobs on the Jenkins machine itself; extra nodes are more servers you add when one machine is not enough. A cloud is a provider configuration (like Amazon EC2) that lets Jenkins start fresh build machines on demand, run the job, and discard them. Both appear under Manage Nodes and Clouds, and both give the same result: more places for builds to run.
14.14.4 Administration and Upgrades
Manage Jenkins also holds admin operations: create users, update users, add security, block GitHub. There is the Jenkins directory location, and execution settings — the demo keeps the maximum parallel executions at two: only two builds run in parallel, more wait in the queue. The quiet period is five minutes, after which the server stops. Upgrades appear there too: most of the time it shows security issues, new plugins, or an older Jenkins version. You can update based on the warnings, or even downgrade — whatever you want. Mostly you will not do this: your infra team does upgrades, and you work in the configuration layer.
Recap: Manage Jenkins is the control room — plugins bring capabilities, global tool configuration wires up real tool installations, nodes and clouds supply machines to build on, and administration handles users, parallelism limits, and upgrades. Next we compare the two main project styles: pipelines and freestyle projects.
14.15 Pipelines vs Freestyle Projects
14.15.1 Project Types
The "New Item" page offers multiple types: freestyle, pipeline, multi-configuration, folder, multi-branch, and organization. Freestyle and pipeline are the most used. Freestyle gives you full freedom — you create however you want. Pipeline is like a pipeline: one stage completes and the next starts, so it suits dependent projects; when multiple projects depend on each other, you put them in a pipeline. Mostly we go for freestyle, and if it does not fit freestyle, we go for pipeline. The others are variations: multi-configuration runs different configurations — one for Android, one for iOS; folder runs a single folder; multi-branch runs multiple branches in a Git directory; organization runs something for the entire organization. Freestyle alone can run five or six projects one by one, easily configured. After selecting freestyle and giving a name, the configuration page opens — everything explained above gets set, and applying it creates the project. An empty freestyle project builds instantly with no work: it says "done" immediately because there is nothing to build.
| Project type | What it is for |
|---|---|
| Freestyle | Full freedom, click-configured jobs; the most used |
| Pipeline | Staged, scripted flows for dependent projects |
| Multi-configuration | Same build with different configurations (Android, iOS) |
| Folder | Groups jobs inside a single folder |
| Multi-branch | Runs jobs per branch in a Git repository |
| Organization | A job that runs for an entire organization |
14.15.2 The Pipeline Script and Its Stages
A pipeline holds the script — Groovy, unless you prefer your own thing — with all steps as stages. The sample pipeline: it uses any agent, uses Maven M3 (the demo machine has Maven 2), has a build stage that pulls the code, starts Maven, and cleans the package; if the clean is successful, it runs a JUnit test; then it creates the jar file in the target. That is the same logic as the POM file, expressed as stages.
Applying and building the sample fails: the console says "M3 not configured — tool type Maven does not have an installation", exactly the global-tool problem. The demo then tried a minimal hello-world pipeline — one stage — and it worked.
Trace: the failing pipeline build. The sample pipeline declares a build stage that says "use Maven M3". The demo machine only has Maven 2 installed, and no Maven installation named M3 was ever defined in Global Tool Configuration. When the build reaches the stage, Jenkins must locate the tool named M3 to attach it to the job — it finds nothing, and the stage fails immediately with "tool type Maven does not have an installation." The fix is not in the pipeline script: it is in Global Tool Configuration, where you register a Maven installation (local path or automatic download) under the same name the pipeline asks for. A minimal one-stage hello-world pipeline runs fine afterwards because it never requests Maven.
The major difference between pipeline and freestyle is the UI: the pipeline gives a beautiful UI with multiple greens — stage complete, stage complete — like a heartbeat for you as it goes on. With freestyle you can only see what happened in the console output. That is the only major difference.
Freestyle vs pipeline — when to pick which. Choose freestyle when you want a click-configured job with full freedom and no scripting; choose pipeline when projects depend on each other and the staged, scripted flow (code → build → test → package as named stages) matches the dependency chain. The visible difference is the UI — stage-by-stage green heartbeat versus a single console log — but the structural difference is that a pipeline encodes the flow as a versionable script, so the pipeline is the natural home for the Jenkinsfile described next.
14.15.3 The Jenkinsfile
For this project a Jenkinsfile also exists, similar to the pipeline script: poll every 10 minutes, disable concurrent builds, use JDK 8, use a Docker agent because the project builds a jar and runs it in a container, go to the M2 directory, set a timeout of 30 minutes after which it should stop, execute run.sh, and in the post section send a message in Slack — something like "danger" or "good" — and send an email. The post section runs after the test stages.
The Jenkinsfile, option by option.
| Setting | What it does |
|---|---|
| Poll every 10 minutes | Jenkins checks the repository on a schedule and builds only when new changes arrive |
| Disable concurrent builds | One build at a time; the next waits in the queue |
| JDK 8 | Builds compile against Java 8 |
| Docker agent | Runs the build inside a container, because the project builds a jar and runs it in a container |
| M2 directory | Points the build at the Maven repository path |
| 30-minute timeout | Kills the build after half an hour instead of hanging forever |
run.sh |
Executes the project's run script |
| Post section | After the test stages: Slack message ("danger" or "good") and email to the team |
Sense-check: every option maps to something we have seen — polling (trigger 5), no concurrency (build environment), JDK and M2 (global tools), timeout (build lock), and post-build notification. The Jenkinsfile is the pipeline script saved as a file in the repository.
14.15.4 Student Questions: Pipelines, Tests, and Agents
Q: For a web application, if we need a system test to run, is it added as a new step? A: Yes, correct. The JUnit test is one kind; a system test is similar to that. You add it as a new step, or you create a shell script with the test inside — either way it works. Or as a third option, in the pipeline you add it as another stage. The doubt — "does a non-JUnit test need special machinery?" — is natural, but any test can be wired in as a new build step, inside the shell script, or as an extra pipeline stage; all three routes run it at the right point of the flow.
Q: To set up pipelines for multiple projects, what do we use? A: A Jenkinsfile can be used — I have one for this project too. Define the stages there, and it runs. There is a Jenkinsfile script in the project; you can also use a Jenkinsfile that is encrypted and decrypt it in Jenkins directly — that is also possible.
Q: Where do we set up Maven so Jenkins finds it? A: Global tool configuration — I showed it right. After installing the plugin, you go to configuration; Maven is under Global Tool Configuration. I already have Maven, so I can give the local Maven path, or I can remove "install automatically" — then Jenkins downloads it from the Apache server, and you can even give which version you want. It downloads that version and builds your application.
Q: Will the script be signed, or can we use an unsigned script? A: If it is a shell script, yes, it can be unsigned — Jenkins will accept it. But for security, keep it encrypted — a signed script only. If that is your question about shell scripts, that is the answer.
Q: How is the runner agent attached to Jenkins? A: For that you need to specify — it is in Manage Nodes and Clouds. There is a built-in node, the local node. To configure your own, you create a new node, which is a server, or a cloud — as of now you do not have one; you download whichever cloud you want, configure the URL and everything, and then use it. From there you can do remote node monitoring. If you have a node, I can show you — the demo was installing the Amazon EC2 plugin: once downloaded, Manage Jenkins shows Amazon EC2 under configure clouds, you give your instance IP, the S3 instance, whatever you have, and configure it.
Q: Azure CLI was not working with the master node; it only worked once an agent was created. Is that a Jenkins limitation? A: It is not running as a master — it is running as a slave agent kind of thing. I am not sure there is any limitation on the Jenkins side. Normally it should execute on the master too. Your Azure could have a limitation: maybe it does not let you run as a master because only administrators have that permission — Azure could have blocked it. It is not a limitation of Jenkins; Jenkins can run everything, and the master node does get executed — it even shows somewhere that the master node is executed. So there is no limitation there.
Recap: Freestyle projects are click-configured with full freedom, pipelines are staged scripts — differing mainly in the stage-by-stage UI — and the Jenkinsfile carries the pipeline definition into the repository with polling, Docker, timeouts, and post-build notifications. Last topic: the Git merge problem that no automation can solve.
14.16 The Git Merge Problem
14.16.1 The Scenario: Two Developers, One Line
This question came from a candidate: two developers are working on the same line. One guy has totally removed the other guy's line and added something completely new in its place.
14.16.2 Why Every Automated Tool Fails
Asked what to do in this situation, the answer is simple to state and hard to do: none of the automated tools help.
Q: Two developers are working on the same line; one removed the other's line and added something new. Will rebase, cherry-pick, or reset fix it? A: Nothing automated will work — not even your stash. The reason: in one line the code was present, in the other line it got removed. That becomes a confusion for Git: which one to keep and which one not to keep? Git cannot decide. Rebase, cherry-pick, and reset all fail for this.
Git cannot decide which line to keep, so the automated tools will fail for this.
Why automation cannot decide. Git's merge machinery works by combining changes on lines that do not overlap. When both developers touched the exact same line, Git faces two competing final states for that one line: Developer A's version and Developer B's version. There is no third piece of information telling Git which state is the intended one — the deletion of the line is itself a change, so keeping both is not a valid option either. Every automated tool — rebase, cherry-pick, reset, stash — ultimately asks Git to make that choice, and Git cannot. This is a genuine conflict that only a human with knowledge of intent can resolve.
14.16.3 The Manual Merge
The only way it works: you manually change. You manually add both lines and commit them as a single commit. You cannot even squash or do anything else — you need to do it manually. And the fix is a team conversation: you need to discuss with each other. Communication is very, very important; you need to do it yourself only.
The manual resolution, step by step. Say the original line was price = cost + tax;. Developer A replaced it with price = cost + tax + shipping;, and Developer B replaced the same line with price = cost + tax * 1.1;. Git sees one line with two new versions.
- The two developers sit down and look at both versions.
- They decide what the line must do — the invoice needs both shipping and the adjusted tax.
- One of them edits the line manually to combine the intent:
price = (cost + tax) * 1.1 + shipping;. - The combined line is committed as a single commit, replacing the conflicted state.
Sense-check: no tool produced that final line — a person did, using knowledge of the business rule that neither Git nor the code can infer. The output is correct only because the humans compared intents.
14.16.4 Why Teams Avoid Overlapping Stories
This is the main reason why, in a particular sprint, the Scrum Master or the Product Owner does not let two people work on similar functionality that touches the same method: two developers should not work on the same method. In the extreme case where it happens anyway, manual merge is the only fix. If somebody in the future asks you to have two developers work on the same thing, explain this problem — and do not take similar stories in the same sprint.
The team rule (professor's warning). Overlapping stories are avoided not by convention but by conflict arithmetic: two developers on the same method mean two edits to the same lines, and the same-line conflict above is the predictable result — a manual merge that automation cannot shortcut. That is why Scrum Masters and Product Owners assign non-overlapping stories per sprint, and if a similar story is ever offered to you in the same sprint as a teammate's, refuse or re-plan it.
Recap: When two developers rewrite the same line, no automated tool can choose which version survives — the only fix is a manual, human-negotiated merge — which is why teams schedule non-overlapping stories in the first place. This closes the session's loop: build with Maven, automate with Jenkins, and coordinate with Git so the pipeline keeps flowing.
Exam Guidance Summary
- The quiz covers Maven and Jenkins only. Focus on the POM file structure, the Maven commands, the cron fields, and the Jenkins configuration concepts from this session.
- On the war-versus-jar question: Jenkins is executed with the jar command; its internal packaging is war. The quiz answer is jar (the confusion in the quiz was flagged for correction).
- Versioning must be known properly to release: 1.0.0 to 2.0.0 is a major change, 1.0.0 to 1.1.0 is a minor enhancement, 1.0.0 to 1.0.1 is an immediate patch. Giving the wrong version misleads customers.
- The five cron fields are minute, hour, day of month, month, and day of week — with the ranges 0–59 minutes, 24-hour clock hours, 1–31 days, 1–12 months, and day names for the week field.
- The five Jenkins build triggers: trigger builds remotely, build after other projects (stable only), build periodically, GitHub hook trigger, and poll SCM — the hook is the efficient, resource-saving way.
- A DevOps engineer should know the monitoring and analytics tools even though they are not covered in depth here, and should know the versioning semantics, the testing tool landscape, and the lint tools per platform.
- The course DevOps process concludes with this session; there is one more tutorial session next month, and remaining doubts can be sent by mail.
Key Industry Applications
- Real-world: CI/CD loops exactly like the one built today — Java project, Maven build, Jenkins deploy, JUnit test — are the standard pattern in industry for Java and Spring Boot services.
- Real-world: the two deployment paths — direct web-server deployment and marketplace packaging (APK for Android, IPA for iOS, WAR, JAR, EXE, DMG, PKG) — explain why one codebase ships to the App Store, Google Play, and company web servers.
- Real-world: App Store and Play Store "minimum version" messages are the build settings of a POM in the wild; the Android version range shows source-and-target thinking.
- Real-world: Flutter's single codebase for all platforms is the analogy for Maven's uniform build across Windows, Mac, Linux, and the cloud.
- Real-world: fat jars like the shade jar appear wherever teams ship self-contained executables; the Docker container idea is the same dependency isolation philosophy.
- Real-world: the
.M2repository is the shared dependency cache that CI machines rely on; clearing it breaks other projects and wastes network bandwidth. - Real-world: GitHub hook triggers and polling are the standard ways production Jenkins farms start builds only when code changes.
- Real-world: Jenkins plugin marketplace covers Ant, Apache, Pipelines, JavaDoc, Docker, Kubernetes, email, and cloud agents such as Amazon EC2 — matching the tool stack of real deployment environments.
- Real-world: Slack notifications and email post-build actions in Jenkinsfiles are the everyday way teams get build status pushed to them.
- Real-world: the Git same-line merge problem is why scrum teams assign non-overlapping stories, and why the merge is manual when overlap happens anyway.
ITD Lecture 14 notes · Maven and Jenkins: Building, Deploying, and Testing a Java Application
Sections Breakdown
The day's agenda: one complete CI/CD loop - code in Git, build with Maven, deploy and schedule with Jenkins, test with JUnit - plus the wider testing, linting, load, and monitoring tool landscape.
How Java source becomes bytecode class files, the parts inside a class file, the just-in-time compiler, and the packaging chain Maven automates.
From IDE to code repository, the two deployment paths, packaging formats per platform, and the layers of a deployment.
Life before build tools, how Ant shrank the steps, what Maven gives you, and the POM as the project's heart.
The core coordinates, packaging, dependencies, parent and modules, properties, scopes, optional dependencies, and student Q&A.
Major, minor, and patch semantics with worked version bumps, and why versioning discipline matters at release.
The core commands, the lifecycle idea, version checks, the Surefire and shade plugins, and compile versus install.
The five cron fields with their ranges, worked example schedules, and wildcards plus non-standard extensions.
The source files and POM, then compile, package, run, and install traced step by step, ending in one shell script.
Vendor coordinates, the JUnit test case, building and testing with dependencies, the Surefire verdict, and the Docker idea.
A jar is a zip, what the original jar and the shaded fat jar hold, and the .M2 repository's role for thin jars.
Jenkins as a Java application, jar versus war, the dashboard's weather symbols and status words, and console output including failures.
General settings and source control, the five build triggers, build environment options, build steps, and post-build actions.
The plugin manager, global tool configuration, nodes and clouds, and administration and upgrades.
Project types, the pipeline script and its stages, the Jenkinsfile, and student Q&A on pipelines, tests, and agents.
Two developers on one line: why every automated tool fails, the manual merge, and why teams avoid overlapping stories.
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.
The Session Plan
Must-know: The session builds one CI/CD loop: Git for code, Maven for building, Jenkins for deploying/scheduling, JUnit for testing; testing tools include Karma, Jasmine, Cucumber, Mockito, Selenium; linters like ESLint; load tools like LoadRunner and JMeter.
⚠️ Top pitfall: Mishearing tool names: Mockito (not Marketo) is the Java mocking framework, Selenium is the automation suite, and APM is a separate monitoring category.
Self-check: Which tool drives a real browser through a web application to verify behavior?
Connects to: Section 14.2
Java Architecture: What Maven Actually Builds
Must-know: Java source -> class file (bytecode) -> jar/war -> run on the OS; class file parts include method area, heap, stack, native stack, GC library, and JIT compiler.
⚠️ Top pitfall: Treating the class file as fully executable: it needs the Java interpreter, and JIT compilation explains the slow-start-fast-later behavior.
Self-check: Why can a Java application feel slow at startup but faster afterwards?
Connects to: Section 14.1, Section 14.9
The Common Deployment Architecture
Must-know: Two deployment paths: direct deployment to a web server vs packaging for a marketplace; packaging format depends on platform (APK/Android, IPA/iOS, WAR/web, JAR/Java, EXE/Windows, DMG/Mac, PKG/Linux).
⚠️ Top pitfall: Forgetting the package format is platform-bound: one codebase produces different packages per target platform.
Self-check: What format does an Android application ship in, and where does it land?
Connects to: Section 14.2, Section 14.4
Maven: The Build Tool That Changed the Game
Must-know: The 'why Maven exists' story: manual builds needed 10 steps, Ant 5, Maven 3; Maven gives easy builds, a uniform cross-platform build (Flutter analogy), quality, and better development practices; the POM is the project's manifest and heart, read bottom-to-top.
⚠️ Top pitfall: Thinking Maven is only for compilation: it also handles dependencies, testing, linting, and quality checks in one POM file.
Self-check: What does the professor recommend when reading an unfamiliar POM file?
Connects to: Section 14.5, Section 14.1
Anatomy of a POM File
Must-know: POM core: modelVersion 4.0.0 is frozen; groupId (company), artifactId (project name), version (three-digit); packaging defaults to jar; scopes are compile (default), provided, runtime, test, system; optional dependencies are not inherited by consumers.
⚠️ Top pitfall: Changing modelVersion 4.0.0, forgetting packaging defaults to jar, using system scope without a hard-coded path (throws an error), and confusing optional with scope.
Self-check: Which scope should JUnit have so it runs during testing but never ships to production?
Connects to: Section 14.4, Section 14.6, Section 14.10
Semantic Versioning
Must-know: Version = major.minor.patch; major change 1.0.0 -> 2.0.0, minor enhancement 1.0.0 -> 1.1.0, immediate patch 1.0.0 -> 1.0.1; the demo POM uses 0.1.0 meaning only small (minor) changes are planned.
⚠️ Top pitfall: Bumping a tiny fix to a major version misleads customers into expecting a major release, damaging trust.
Self-check: What does moving from 1.0.0 to 1.1.0 communicate to users?
Connects to: Section 14.5, Section 14.7
Maven Commands and Plugins
Must-know: mvn install = compile + package + test + copy to .M2 in one shot; compile is only the first step; mvn --version and mvn -v are the only valid version checks; Surefire runs JUnit tests; shade creates the fat jar.
⚠️ Top pitfall: Running plain mvn version throws an error - only mvn --version and mvn -v work; also confusing compile with install because install additionally copies the artifact into .M2.
Self-check: Which Maven command copies the built jar into the local .M2 dependency directory?
Connects to: Section 14.5, Section 14.11
Cron Jobs: Scheduling Scripts and Builds
Must-know: Five cron fields in order: minute, hour, day of month, month, day of week; ranges 0-59, 0-23, 1-31, 1-12, day names; * means every value; 2:35 PM daily = 35 14 * * *; every minute = * * * * *.
⚠️ Top pitfall: Using a 12-hour clock (2 PM written as 2 instead of 14), writing minute 60, or relying on non-standard extensions like 'at monthly' which differ across systems.
Self-check: Write the cron schedule for 12:00 noon every day.
Connects to: Section 14.13, Section 14.12
Building Hello World: The Worked Demo
Must-know: Lifecycle: mvn --version prints Maven 3.8.6; mvn compile compiles 2 sources to target/classes; mvn package builds gs-maven-0.1.0.jar and the shaded jar; java -jar prints hello world; mvn install copies to .M2/repository; maven.sh runs clean, compile, package, run, test, install in one script.
⚠️ Top pitfall: Running Maven from inside the target directory throws an error because the source folder and POM cannot be found - always run from the project root.
Self-check: Where does mvn install copy the built jar, and why is the whole path needed for a thin jar?
Connects to: Section 14.7, Section 14.11, Section 14.10
Adding Dependencies: Joda-Time and JUnit
Must-know: Dependencies are declared with vendor coordinates; JUnit uses scope test so it never ships to production; Surefire counters (tests run, failures, errors, skipped) read zero except tests = 1; source and target Java versions may differ because the fat jar carries all dependencies.
⚠️ Top pitfall: Declaring JUnit without test scope ships it to production; believing source and target must be the same Java version when the fat jar makes them independent.
Self-check: Which Surefire counter would be non-zero if the hello assertion failed?
Connects to: Section 14.5, Section 14.9, Section 14.11
Inside the Jar: Fat Jar vs Thin Jar
Must-know: mvn clean deletes only the target directory, never .M2 (shared repository with other projects' dependencies); fat jar bundles all dependencies (runs alone), thin jar needs the shared dependency path; the manifest's main class is what Maven executes.
⚠️ Top pitfall: Thinking mvn clean clears .M2: it would break other projects and force re-downloads, wasting network bandwidth.
Self-check: Why does a fat jar print the local time while the original thin jar does not?
Connects to: Section 14.7, Section 14.10, Section 14.9
Jenkins: Setup, Dashboard, and Console Output
Must-know: Jenkins runs with the jar command; war is only its internal packaging - quiz answer is jar; default port 8080; localhost is 127.0.0.1; weather symbols summarize build health; console output shows the full lifecycle per build.
⚠️ Top pitfall: Choosing war on the quiz because Jenkins prints 'Jenkins.war' at startup - the execution command is jar.
Self-check: Which port does Jenkins use by default, and why did the demo use 5050?
Connects to: Section 14.9, Section 14.13
Configuring a Jenkins Project
Must-know: Five build triggers: trigger builds remotely, build after other projects (stable condition), build periodically (cron), GitHub hook trigger, poll SCM; the hook is efficient because it builds only when code changes.
⚠️ Top pitfall: Enabling 'delete entire workspace before build' destroys the whole codebase with no recovery; also enabling concurrent builds risks corrupted builds.
Self-check: Which build trigger is the recommended efficient way, and why?
Connects to: Section 14.12, Section 14.8
Managing Jenkins
Must-know: Plugins add capabilities; Maven is configured under Global Tool Configuration with a local path or 'install automatically'; nodes/clouds attach build machines; max parallel executions queues extra builds.
⚠️ Top pitfall: Installing the Maven plugin but never configuring a Maven installation - the build then fails with 'tool type Maven does not have an installation'.
Self-check: Where in Jenkins do you point Jenkins to the Maven it should use?
Connects to: Section 14.15, Section 14.13
Pipelines vs Freestyle Projects
Must-know: Freestyle = click-configured with full freedom; pipeline = staged script for dependent projects; the only major UI difference is the stage-by-stage green view; the Jenkinsfile polls, disables concurrency, sets JDK/Docker, timeout, and posts to Slack/email; Maven tools must be registered in Global Tool Configuration.
⚠️ Top pitfall: A pipeline asking for a Maven tool that was never installed fails with 'tool type Maven does not have an installation' - fix it in Global Tool Configuration, not in the script.
Self-check: Where does the Jenkinsfile tell Jenkins which agent to run the build on?
Connects to: Section 14.14, Section 14.13
The Git Merge Problem
Must-know: Same-line conflicts cannot be resolved by rebase, cherry-pick, reset, or stash - Git cannot decide which line to keep; the only fix is a manual merge and a team conversation, and teams avoid overlapping stories in a sprint.
⚠️ Top pitfall: Expecting an automated Git command to resolve a same-line conflict - any tool that delegates the choice to Git fails.
Self-check: Why can no automated Git tool resolve two developers editing the same line?
Connects to: Section 14.3
Exam Guidance Summary
Must-know: Quiz scope: Maven and Jenkins only - POM file structure, Maven commands, cron fields, Jenkins configuration; jar (not war) is how Jenkins is executed; the GitHub hook is the efficient build trigger.
⚠️ Top pitfall: Writing war on the war-versus-jar question - the execution command is jar even though the internal packaging is war.
Self-check: Which five cron fields and ranges must be known for the quiz?
Connects to: Section 14.6, Section 14.8, Section 14.13
Key Industry Applications
Must-know: The exact demo pattern - Java project, Maven build, Jenkins deploy, JUnit test - is how industry runs Java and Spring Boot services, with hook-triggered builds and post-build Slack/email notifications.
⚠️ Top pitfall: Clearing the .M2 repository on CI machines - it is the shared dependency cache; clearing it breaks other projects and wastes network bandwidth.
Self-check: Why does one codebase end up as both an APK and a WAR?
Connects to: Section 14.3, Section 14.9, Section 14.11
Was this lecture useful?
BitsNotes AI Assistant
Subject Notes AssistantConfigure AI Chat
Choose how to access the chatbotSigned in as
Powered by BitsNotes — 20 messages per day. No API key needed. Want unlimited access? Use "Bring Your Own Key" mode.
Sign in to use AI Chat
Get 20 free AI messages per day to ask questions about your lecture notes. Sign in with Google or GitHub — it takes 5 seconds.
Sign In to BitsNotesSwitch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.