What is a dependency management tool?#
A dependency management tool is a software system or utility that automates the process of identifying, retrieving, updating, and maintaining the external libraries or packages (referred to as dependencies) required by a software project. It ensures that all necessary dependencies are included and managed in a standardised way, which helps prevent version conflicts, missing libraries, and manual errors during software development.
- What is a dependency management tool?
- The classical Dependency manager for Java - Maven
- BLD - the lightweight Java Build System
- The difference between Maven and BLD
- How to start with bld?
- An Example migration of a project from Maven to bld
- Conclusion
Demo - Project are available here: https://github.com/Java-Publications/Blog---Core-Java---2024.08---bld-the-lightweight-build-tool
Here’s how dependency management tools generally function:
Dependency Identification:#
Developers list the libraries or frameworks their project depends on in a configuration file (like Maven’s pom.xml or Gradle’s build.gradle). Each dependency is defined by attributes such as group ID , artifact ID , and version.
Example (Maven) :
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>Fetching Dependencies:#
The tool retrieves the required dependencies from repositories (such as Maven Central or custom/private repositories) and downloads them to a local repository on the developer’s machine. These repositories host many external libraries, frameworks, and plugins.
Local Repository : A cache stored locally on a developer’s machine to prevent repeated downloads.
Remote Repository : Centralized or custom servers hosted by the dependencies (e.g., Maven Central).
Version Management:#
Dependency management tools handle dependency versioning , ensuring the correct versions of external libraries are used. Tools like Maven or Gradle support transitive dependency resolution , meaning if a dependency relies on another library, that secondary library will also be fetched automatically.
Version Conflicts : If two libraries depend on different versions of the same dependency, the tool resolves the conflict by selecting the nearest version in the dependency tree or the explicitly defined one.
Scope & Environment Management:#
Dependency management tools allow for different scopes or profiles that specify when a dependency is required (e.g., only during testing or in a production environment). This helps optimise resource usage and prevent unnecessary dependencies from being included in the final build.
Example Scopes :
Compile : Dependencies available during compilation and runtime.
Test : Dependencies used only during testing (e.g., JUnit).
Provided : Dependencies expected to be supplied by the runtime environment.
Build Process Integration:#
Dependency management tools integrate into a project’s build lifecycle. They ensure that the right versions of libraries are fetched before compiling, packaging, or testing the application. For instance, Maven’s **install** phase ensures that all dependencies are installed in the local repository before building.
Popular Dependency Management Tools:#
Maven : Uses **pom.xml** to define dependencies and follows a declarative model with predefined lifecycles.
Gradle : Uses Groovy or Kotlin DSL for its build scripts, offering more flexibility and allowing for custom build logic.
npm (Node Package Manager) : Manages dependencies for JavaScript and Node.js projects, using a **package.json** file.
bld : A newer tool for Java projects that allows developers to define dependencies directly in Java code.
Benefits of Dependency Management Tools:#
Automation : They automate the retrieval and management of libraries, removing the need for developers to download and include them manually.
Consistency : Ensures that every developer in a team uses identical versions of dependencies, leading to more consistent builds.
Conflict Resolution : Handles version conflicts between dependencies automatically, preventing runtime errors.
Efficiency : By caching dependencies locally, tools reduce repeated downloads and improve build times.
Security : Modern tools often check for security vulnerabilities in dependencies and alert developers to updates.
Example Workflow:#
A developer declares dependencies in a configuration file.
The tool checks the local repository for cached versions of the libraries.
If dependencies aren’t cached, the tool fetches them from a remote repository.
The tool ensures all dependencies are included during the build process (compile, test, package, etc.).
Dependency management tools are vital for modern software development, especially in large projects with numerous external libraries. They automate the complex process of handling dependencies, reducing manual errors, improving efficiency, and ensuring the project builds correctly across different environments.
The classical Dependency manager for Java - Maven#
Maven is a build automation and project management tool primarily used in Java projects. It uses a declarative model to manage a project’s build, reporting, and dependencies using an XML configuration file called **pom.xml** . Below is an overview of how Maven works:
Project Structure and pom.xml#
At the heart of every Maven project is the **pom.xml** file (Project Object Model). This file defines the project structure, dependencies, build plugins, and goals. Here’s what **pom.xml** generally contains:
Group ID : Identifies the project’s group (often based on package naming).
Artifact ID : A unique identifier for the project.
Version : Version of the project being built.
Dependencies : A list of libraries or frameworks the project requires (such as **JUnit** for testing or **Spring** for dependency injection).
Plugins : Additional tools that enhance the build process (for tasks like creating JAR files, running tests, etc.).
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>Build Lifecycle#
Maven operates on a series of lifecycles. The default lifecycle consists of a sequence of build phases that are invoked when building a project:
- validate : Check if the project is correct and all necessary information is available.
- compile : Compile the source code of the project.
- test : Run tests on the compiled code using a suitable testing framework (e.g., JUnit).
- package : Package the compiled code into a distributable format, like a JAR or WAR.
- install : Install the package into the local repository for use as a dependency in other local projects.
- deploy : Copy the final package to the remote repository for sharing with other developers.
These lifecycle phases are sequential, meaning invoking **install** will also execute all phases before it (from **validate** to **package** ).
Dependency Management#
Maven’s most powerful feature is its dependency management system. Maven fetches external libraries from Maven Central , a global repository that hosts many open-source libraries. Maven uses transitive dependency resolution , meaning that if Project A depends on Library B and Library B depends on Library C, Maven will automatically fetch Library C.
Dependencies are defined in the **pom.xml** under the **< dependencies>** section, as shown above. Maven manages and can automatically update versions, making integrating libraries very efficient.
Maven Repositories#
Maven uses three types of repositories:
Local Repository : This is on the developer’s machine. When Maven builds a project, it checks the local repository first to resolve dependencies.
Central Repository : Maven Central is the default remote repository where Maven looks for dependencies that are not available in the local repository.
Remote Repository : Additional remote repositories, such as private or custom repositories that host specific artefacts, can be defined.
Plugins#
Plugins in Maven extend its functionality. Some common Maven plugins include:
maven-compiler-plugin : Compiles the source code.
maven-surefire-plugin : Runs unit tests.
maven-jar-plugin : Packages the project as a JAR file.
maven-war-plugin : Packages the project as a WAR file for web applications.
These plugins are usually added to the **pom.xml** under the **< build>** section.
Profiles
Maven also supports profiles, which allow developers to define different configurations for different environments (e.g., development, testing, production). Profiles are specified in the pom.xm l and can be activated by different triggers, such as system properties or environments.
<profiles>
<profile>
<id>dev</id>
<properties>
<env>development</env>
</properties>
</profile>
</profiles>Multi-Module Projects
Maven can handle multi-module projects , where a single project is divided into multiple sub-projects or modules. Each module typically has its own **pom.xml** but is coordinated by a parent POM. This feature is helpful for large projects with independent yet related components.
How It All Works Together:#
1. The developer defines the project structure and dependencies in the pom.xml.
2. Maven processes this file and resolves all dependencies by downloading required JAR files from remote repositories (if they are not found in the local repository).
3. Maven executes phases from the build lifecycle, compiling, testing, and packaging the project.
4. Plugins add extra functionality, such as running tests, generating documentation, and creating artefacts.
Maven’s declarative model and lifecycle management make it a popular choice for managing complex Java projects. Its rich plugin ecosystem and dependency management reduce manual effort while standardising the build process. However, the XML-heavy configuration can be cumbersome for some developers, especially compared to newer build tools like bld.
BLD - the lightweight Java Build System#
bld is a lightweight build tool designed specifically for the Java ecosystem. Unlike traditional build tools like Maven or Gradle, which rely on declarative syntax and often involve complex configurations, bld allows developers to write their build logic in pure Java. This approach simplifies the learning curve and reduces the cognitive load on developers by enabling them to stay within the Java language for both application and build logic, making it a particularly modern and streamlined tool for Java projects.
Key Features of bld#
Java-Based Build Logic : The most distinctive aspect of bld is that it uses Java itself to define the build process. This eliminates the need for learning domain-specific languages (DSLs) or XML configurations as is the case with Maven or Gradle. With bld , build logic becomes part of the Java code, making it easy to reason about and maintain in the same environment where application development occurs.
Explicit Task Execution : bld emphasises a transparent and predictable execution model. Tasks in bld do not run automatically but must be explicitly triggered, giving developers complete control over the build process. This removes any “auto-magical” behaviour often associated with other tools, which can sometimes lead to unexpected outcomes.
Simple Dependency Management : Dependency management in bld is intuitive and leverages Java constructs. Dependencies are managed through a standard library system, and they can be added directly in the Java build script by specifying the Maven coordinates. This system allows bld to handle fetching and resolving dependencies automatically, similar to Maven or Gradle, but with less complexity.
Integration with Java 17 : bld takes full advantage of the latest Java features, relying on Java 17 as its minimum requirement. This allows developers to use modern Java syntax, including records, sealed classes, and pattern matching, to write concise and clean build scripts. The tool supports features like code auto-completion and Javadoc integration, making it easy to navigate within popular Integrated Development Environments (IDEs).
IDE Support : bld has integrations for popular IDEs like IntelliJ IDEA, offering features such as automatic project detection, quick access to the main build class, and the ability to run multiple build commands directly from the IDE. This smooths the workflow for developers who prefer using graphical interfaces over command-line interactions.
Command-Line Interface (CLI) : The bld CLI offers a set of standard commands that allow developers to compile, run, test, and package their projects. It supports everyday build tasks like creating JAR files, managing dependencies, running tests, and generating Javadoc. Developers can also define custom commands within their build scripts using Java, extending the tool’s flexibility.
How bld Stands Out in the Java Ecosystem#
In the Java build ecosystem, tools typically fall into two categories: declarative (like Maven) and code-based (like Gradle). bld is part of the second group but adds the advantage of being immediately executable without predefining build plans. This immediate execution reduces the complexity of managing and understanding the build pipeline.
For developers familiar with scripting environments like Node.js or Go, bld ’s ergonomics offer a familiar, streamlined experience. It delivers the ease of dependency management while maintaining transparency and direct control, helping to avoid common issues associated with over-engineered build systems.
Usage Example#
A basic bld file for a Java project could look like this:
package com.example;
import rife.bld.Project;
import java.util.List;
import static rife.bld.dependencies.Repository.*;
import static rife.bld.dependencies.Scope.*;
public class MyappBuild extends Project {
public MyappBuild() {
pkg = "com.example";
name = "Myapp";
mainClass = "com.example.MyappMain";
version = version(0, 1, 0);
repositories = List.of(MAVEN_CENTRAL);
scope(compile).include(dependency("com.fasterxml.jackson.core",
"jackson-databind",
version(2, 16, 0)));
}
}In this setup, a developer specifies dependencies in the build file itself, alongside build configurations, removing the need for separate files like **pom.xml** in Maven.
bld offers a powerful yet simple alternative for Java developers who seek to avoid the complexities of traditional build tools. Writing build logic in Java, using an explicit task execution model, and supporting modern Java features provide a clear and concise way to manage builds, dependencies, and distribution. It suits developers who prefer a code-centric approach to their entire development lifecycle, from coding to building.
The difference between Maven and BLD#
bld and Maven are both Java build tools, but they differ significantly in their approach, complexity, and usage. Here’s a comparison of the two:
Build Language:#
Maven : Uses an XML-based configuration file called **pom.xml** (Project Object Model). This declarative approach requires developers to define a static configuration of dependencies, plugins, and tasks in a verbose XML format.
bld : It uses Java as the build scripting language, meaning developers write their build logic using Java code. This approach leverages Java’s advantages, such as type safety, auto-completion, and the ability to refactor using IDE tools.
Declarative vs. Code-Based:#
Maven : Fully declarative. Developers declare what they want to happen, and Maven uses its lifecycle to determine how to execute tasks. This provides predictability but can be hard to customise beyond its standard behaviour.
bld : Primarily code-based. Developers write Java code that is executed immediately. This provides more flexibility and control over the build process, allowing custom build flows and logic to be easily implemented.
Task Execution:#
Maven : Its lifecycle uses predefined phases, such as **validate** , **compile** , **test** , **package** , and **install** . Each phase automatically triggers a set of goals, which is convenient for standard projects but can become cumbersome if you need custom behaviour outside of Maven’s predefined lifecycle.
bld : Offers explicit control over tasks. Tasks are not automatically linked in a lifecycle; developers must call them explicitly, which provides more control and avoids the hidden behaviour common in Maven.
IDE Integration:#
Maven : Has widespread support across IDEs like Eclipse, IntelliJ IDEA, and NetBeans. It has been around for a long time, so IDEs can automatically recognise Maven projects and provide support like dependency management, project configuration, and running lifecycle phases directly from the IDE.
bld : Offers integration with IntelliJ IDEA and leverages the build logic as Java code, providing features such as code navigation, auto-completion, and even IDE-run configurations.
Dependency Management:#
Maven : Dependency management is one of Maven’s most vital features. It uses a central repository (Maven Central) and follows a transitive dependency model, automatically downloading dependencies and their sub-dependencies. However, this sometimes results in dependency conflicts or versioning issues.
bld : Similar to Maven, bld supports dependency management using Maven Central or other repositories. However, it allows developers to handle dependency logic within the Java build file itself, which can simplify certain aspects of dependency resolution.
Build Artifacts:#
Maven : Offers a standardised way to build JAR, WAR, and other packages using plugins. It also supports additional artefact types like sources, documentation, and testing.
bld : Supports similar build artifacts (JAR, UberJAR, etc.) but gives developers more direct control over how these are generated. For example, developers can include custom commands to create specific outputs like precompiled templates.
Complexity and Learning Curve:#
Maven : Can be complex, especially when dealing with multi-module projects or needing custom behaviour that requires a deep understanding of Maven’s plugin architecture and lifecycle phases. Modifying or extending Maven’s behaviour often involves writing custom plugins or extensive XML configuration.
bld : Aims to reduce complexity by allowing developers to manage builds using familiar Java code. It is simpler for Java developers to grasp, and writing custom build logic feels more natural compared to learning Maven’s XML configuration and plugins.
Performance:#
Maven : Due to its lifecycle model and plugin architecture, Maven can be slower, mainly when performing complete builds with many plugins or complex dependency graphs. Incremental builds and caching are not as optimised as some newer tools.
bld : Provides faster builds in many cases due to its simpler execution model and the immediate execution of Java code without going through multiple lifecycle phases.
How to start with bld?#
The easiest way to start with bld is on commandline. For this go into the directory where you want to start with the project. Open a terminal and execute the following command that is available at the home page of the project:
bash -c "$(curl -fsSL https://rife2.com/bld/create.sh)"
Downloading bld v2.1.0...
Welcome to bld v2.1.0.
Please enter a number for the project type:
1: base (Java baseline project)
2: app (Java application project)
3: lib (Java library project)
4: rife2 (RIFE2 web application)This will download the bld-Script and execute it. Now, you can choose what kind of project you want to start. I am selecting 1 for a baseline project. The next questions are package name and application name. With this informationes the generating process will be done. You will find a directory with the appname inside the directory. In my example it is the name core.
Inside the app directory there is a script for unix/osx and one for windows. This is the wrapper for bld. I am using the osx version in my example here.
To test if the installation is correct, use the command ./bld version. In my case the answer is 2.6.0.
There are two directories. The first one is called lib. Inside there are different directories, one is called bld and the others are named by the different scopes you know from the maven lifecycles. (compile , provided , runtime and test). Later you will find the dependencies that are required for the different scopes inside these directories. The most important directory right now, is the directory called bld. Inside the directory there is the build related files like the wrapper, properties and cache. For customizing the build environment, the properties file is the right place to go. Here you can activate extensions for example. But this will be dome later.
Back at the root directory of the project, there is the src folder as well. Inside the source folder are three subfolders. The src and test folder is known from maven already. The third folder called bld contains the sources for the build configuration. The Class is called appname plus “Build” in this case it is CoreBuild.
Here we can start defining the build process itself.
An Example migration of a project from Maven to bld#
Migrating a Maven project to bld involves several steps, as bld operates on Java-based build scripts instead of Maven’s **pom.xml** XML-based configuration. Here’s a general example of how you might approach migrating a Maven project to bld.
The Maven Project Structure#
Before migrating, you need to analyse the Maven project structure:
**pom.xml** : This file contains dependencies, plugins, repositories, build profiles, and other configurations.
We will go through different sections of the pom.xml and transforming them into the bld version.
<groupId>com.svenruppert</groupId>
<artifactId>core</artifactId>
<version>0.0.1-SNAPSHOT</version>Start by creating a new bld build script in Java. bld uses pure Java code to define the build logic, dependencies, and configurations, allowing you to migrate the settings from the **pom.xml** . First, create a **Build.java** file (or similar) in your **src/bld/java/** directory, if not already existing. Here is a starting point for the migration:
package com.svenruppert;
import rife.bld.Project;
import rife.bld.dependencies.VersionNumber;
import java.nio.file.Path;
import java.util.List;
public class CoreBuild extends Project {
public CoreBuild() {
pkg = "com.svenruppert";
name = "Core";
mainClass = "com.svenruppert.Application";
version = version(0, 1, 0,"SNAPSHOT");
public static void main(String[] args) {
new CoreBuild().start(args);
}
}As next we are migrating the definition of the repositories.
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>libs-release</name>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
<repository>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
<id>snapshots</id>
<name>libs-snapshot</name>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
</repositories>This will lead to a statement like the following inside the Build-Class.
repositories = List._of_(_MAVEN_CENTRAL_ , _RIFE2_RELEASES_);For the most projects you will need some dependencies in different scopes. The xml - Version wil look like the following. I am not listing all the dependencies of the demo project.
<dependency>
<groupId>io.javalin</groupId>
<artifactId>javalin</artifactId>
<version>6.3.0</version>
</dependency>
<dependency>
<groupId>io.javalin</groupId>
<artifactId>javalin-testtools</artifactId>
<version>6.3.0</version>
<scope>test</scope>
</dependency>The bld Version:
VersionNumber versionJavalin = version(6, 3, 0);
scope(compile)
.include(dependency("io.javalin", "javalin", versionJavalin));
scope(test)
.include(dependency("io.javalin", "javalin-testtools", versionJavalin));Finally we a have to migrate the plugins from maven to bld. If your Maven project used custom lifecycle phases, you can create custom commands in bld using Java methods annotated with **@BuildCommand** . This allows you to replicate any special build steps that were part of your Maven process, like packaging, publishing, or precompilation tasks.
Example of adding a custom command in bld :
@BuildCommand
public void customTask() {
System.out.println("Running custom task...");
}Here we are at a point, where we can see, that bld is not as feature ritch as maven out of the box. But, if you need support for another plugin or lifecycle, have a look at the documentation about extensions. Here you will find all you need to create one by yourself. The cool thing here is, that everything can be done on core java. Even complex tasks can be implemented including all technologies that are needed for this step.
But, back to the migration. In my case I am using the pitest plugin to generate the mutation test coverage reports.
Inside the pom.xml you have to declare the following:
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.14.1</version>
<dependencies>
<dependency>
<groupId>org.pitest</groupId>
<artifactId>pitest-junit5-plugin</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
<configuration>
<threads>2</threads>
<outputFormats>
<outputFormat>XML</outputFormat>
<outputFormat>HTML</outputFormat>
</outputFormats>
<targetClasses>
<param>${pitest-prod-classes}</param>
<!--<param>org.reflections.*</param>-->
</targetClasses>
<targetTests>
<param>${pitest-test-classes}</param>
</targetTests>
</configuration>
</plugin>The corresponding bld part will be:
scope(test)
.include(dependency("org.pitest", "pitest", version(1, 17, 0)))
.include(dependency("org.pitest", "pitest-command-line", version(1, 17, 0)))
.include(dependency("org.pitest", "pitest-junit5-plugin", version(1, 2, 1)))
.include(dependency("org.pitest", "pitest-testng-plugin", version(1, 0, 0)));
@BuildCommand(summary = "Run PIT mutation tests")
public void pit() throws Exception {
new PitestOperation()
.fromProject(this)
.reportDir(Path.of("reports", "mutations").toString())
.targetClasses(pkg + ".*")
.targetTests("junit." + pkg + ".*")
.verbose(true)
.execute();
}Now, you are able to trigger the pitest extension on commandline with ./bld pitest. To activate the plugin there must be the declaration inside the file “lib/bld/bld-wrapper.properties ”. The value of this key is the plugin maven coordinates including the version number.
bld.extensions=com.uwyn.rife2:bld-pitest:1.0.2With this we are able to migrate maven project into bld-projects.
Conclusion#
Migrating a complex Maven project to bld involves translating the dependency management, build logic, and testing configurations from pom.xml into Java code using bld ’s project class structure. While both tools manage dependencies and build processes, bld allows developers to write build logic in Java, giving more flexibility and making it easier to manage as part of the application codebase.
With bld , you gain direct control over the build process through Java code, but you must ensure that all elements of your Maven build (dependencies, plugins, phases) are correctly transferred.
Happy Coding
Sven





