Introduction

Software reuse, a long time advocated software engineering practice (Naur and Randell 1969; Krueger 1992), has boomed in the last years thanks to the widespread adoption of build automation and package managers (Cox 2019; Soto-Valero et al. 2019). Package managers provide both a large pool of reusable packages, a.k.a. libraries, and systematic ways to declare what are the packages on which an application depends. Examples of such package management systems include Maven for Java, npm for Node.js, or Cargo for Rust. Build tools automatically fetch all the packages that are needed to compile, test, and deploy an application.

Package managers boost software reuse by creating a clear separation between the application and its third-party dependencies. Meanwhile, they introduce new challenges for the developers of software applications, who now need to manage those third-party dependencies (Cox 2019) to avoid entering into the so-called “dependency hell”. These challenges relate to ensuring high quality dependencies (Salza et al. 2019), keeping the dependencies up-to-date (Bavota et al. 2015), or making sure that heterogeneous licenses are compatible (Wu et al. 2017).

Our work focuses on one specific challenge of dependency management: the existence of bloated dependencies. This refers to packages that are declared as dependencies for an application, but that are actually not necessary to build or run it. The major issue with bloated dependencies is that the final deployed binary file includes more code than necessary: an artificially large binary is an issue when the application is sent over the network (e.g., web applications) or it is deployed on small devices (e.g., embedded systems). Bloated dependencies could also embed vulnerable code that can be exploited, while being actually useless for the application (Gkortzis et al. 2019). Overall, bloated dependencies needlessly increase the difficulty of managing and evolving software applications.

We propose a novel, unique, and large scale analysis of bloated dependencies. So far, research on bloated dependencies has been only touched with a study of copy-pasted dependency declarations by McIntosh et al. (2014), and a study of unused dependencies in the Rust ecosystem by Hejderup et al. (2018). Our previous work gives preliminary results on this topic in the context of Java (Harrand et al. 2019). Yet, there is no systematic analysis of the presence of bloated dependencies nor about the importance of this problem for developers in the Java ecosystem.

Our work focuses on Maven, the most popular package manager and automatic build system for Java and languages that compile to the JVM. In Maven, developers declare dependencies in a specific file, called the POM file. In order to analyze thousands of artifacts on Maven Central, the largest repository of Java artifacts, manual analysis is not a feasible solution. To overcome this problem, we have developed DepClean, a tool that performs an automatic analysis of dependency usage in Maven projects. Given an application and its POM file, DepClean collects the complete dependency tree (the list of dependencies declared in the POM, as well as the transitive dependencies) and analyzes the bytecode of the artifact and all its dependencies to determine the presence of bloated dependencies. Finally, DepClean generates a variant of the POM in which bloated dependencies are removed.

Armed with DepClean, we structured our analysis of bloated dependencies in two parts. First, we automatically analysed 9,639 artifacts and their 723,444 dependencies. We found that 75.1% of these dependencies are bloated. We identify transitive dependencies and the complexities of dependency management in multi-module projects as the primary causes of bloat. Second, we performed a user study involving 30 artifacts, for which the code is available as open-source on GitHub and which are actively maintained. For each project, we used DepClean to generate a POM file without bloated dependencies and submitted the changes as a pull request to the project. Notably, our work yielded 21 merged pull requests by open-source developers and 140 bloated dependencies were removed.

To summarize, this paper makes the following contributions:

  • A comprehensive study of bloated dependencies in the context of the Maven package manager. We are the first to quantify the magnitude of bloat on a large scale (9,639 Maven artifacts) showing that 75.1% of dependencies are bloated.

  • A tool called DepClean to automatically analyze and remove bloated dependencies in Java applications packaged with Maven. DepClean can be used in future research on package management as well as by practitioners.

  • A qualitative assessment of the opinion of developers regarding bloated dependencies. Through the submission of pull requests to notable open-source projects, we show that developers care about removing dependency bloat: 21/26 of answered pull requests have been merged, removing 140 bloated dependencies.

The remainder of this paper is structured as follows. Section 2 introduces the key concepts about dependency management with Maven and presents an illustrative example. Section 3 introduces the new terminology and describes the implementation of DepClean. Section 4 presents the research questions that drive our study, as well as the methodology followed. Section 5 covers our experimental results for each research question. Sections 6 and 7 provide a comprehensive discussion of the results obtained and present the threats to the validity of our study. Section 8 concludes this paper and provides future research directions.

Background

We provide an overview of the Maven package management system and of the essential terminology. We illustrate these concepts with a concrete example.

Maven Dependency Management Terminology

Maven is a popular package manager and build automation tool for Java projects and other languages that compile to the JVM (e.g., Scala, Kotlin, Groovy, Clojure, or JRuby). Maven relies on a specific build file, known as the POM (acronym for “Project Object Model”), where developers specify information about the project, its dependencies and its build process. POM files can inherit from a base POM, known as the Maven parent POM. The inheritance and declaration of dependencies is a design decision of developers.

Maven Project

A Maven project includes source code files and build files. It can be a single-module, or a multi-module project. The former has a single POM file, which includes all the dependencies and build instructions to produce a single artifact (JAR file). The latter allows to separately build multiple artifacts in a certain order through a so-called aggregator POM. In multi-module projects, developers can define a parent POM that specifies the dependencies used by all the modules.

Maven Artifact

We refer to artifacts as compiled Maven projects that have been deployed to a binary code repository. A Maven artifact is typically a JAR file that is uniquely identified with a triplet (G:A:V ), G, the groupId, identifies the organization that develops the artifact, A, the artifactId, is the name of the artifact, and V corresponds to its version. Maven Central is the most popular public repository to host Maven artifacts.

Dependency Resolution

Maven resolves dependencies in two steps: (1) based on the POM file(s), it determines the set of required dependencies, and (2) it fetches dependencies that are not present locally from external repositories such as Maven Central. Maven constructs a dependency tree, that captures all dependencies and their relationships. Given one artifact, we distinguish between three types of Maven dependencies: direct dependencies that are explicitly declared in the POM file; inherited dependencies, which are declared in the parent POM; and transitive dependencies obtained from the transitive closure of direct and inherited dependencies. Dependency version management is a key feature of the dependency resolution, which Maven handles with a specific dependency mediation algorithm.Footnote 1

A Brief Journey in the Dependencies of the Jxls Library

We illustrate the concepts introduced previously with one concrete example: Jxls,Footnote 2 an open source Java library for generating Excel reports. It is implemented as a multi-module Maven project with a parent POM in jxls-project, and three modules: jxls, jxls-examples, and jxls-poi.

Listing 1 shows an excerpt of the POM file of the jxls-poi module, version 1.0.15. It declares jxls-project as its parent POM (lines 1 − 5) and a direct dependency towards the poi Apache library (lines 10–14). Figure 1 depicts an excerpt of its Maven dependency tree (we do not show testing dependencies here, such as JUnit, to make the figure more readable). Nodes in blue, pink, and yellow represent direct, inherited, and transitive dependencies, respectively, for the jxls-poi artifact (as reported by the dependency:tree Maven plugin).

Listing 1
figure e

Excerpt of the POM file corresponding to the module jxls-poi of the multi-module Maven project Jxls

Fig. 1
figure 1

Excerpt of the dependency tree of the multi-module Maven project Jxls (dependencies used for testing are not shown for the sake of simplicity)

The library jcl-over-slf4j declares a dependency towards slf4j-api, version 1.7.12, which is omitted by Maven since it is already added from the jxls-project parent POM. On the other hand, Jxls declares dependencies to version 1.7.26 of jcl-over-slf4j and slf4j-api, but the lower version 1.7.12 was chosen over it since it is nearer to the root in the dependency tree and, by default, Maven resolves version conflicts with a nearest-wins strategy. Once Maven finishes the dependency resolution, the classpath of jxls-poi includes the following artifacts: poi, commons-codec, commons-collections4, commons-jexl, commons-logging, jxls, commons-jex13, commons-beanutils, commons-collections, logback-core, jcl-over-slf4j, and slf4j-api. The goal of our work is to determine if all the artifacts in the classpath of Maven projects such as jxls-poi are actually needed to build and run those projects.

Bloated Dependencies

In this section, we introduce the idea of bloated dependency, which is the fundamental concept presented and studied in the rest of this paper. We describe our methodology to study bloated dependencies, as well as our tools to automatically detect and remove them from Maven artifacts.

Dependencies among Maven artifacts form a graph, according to the information declared in their POMs. This graph has been introduced in our previous work about the Maven Dependency Graph (MDG) (Benelallam et al. 2019). The MDG is defined as follows:

Definition 1 (Maven Dependency Graph)

The MDG is a vertex-labelled graph, where vertices are Maven artifacts (uniquely identified by their G:A:V coordinates), and edges represent dependency relationships among them. Formally, the MDG is defined as \(\mathcal {G = (\mathcal {V}, \mathcal {E})}\), where:

  • \(\mathcal {V}\) is the set of artifacts in the Maven Central repository

  • \(\mathcal {E} \subseteq \mathcal {V} \times \mathcal {V}\) represent the set of directed edges that determine dependency relationships between each artifact \(v \in \mathcal {V}\) and its dependencies

Novel Concepts

Each artifact in the MDG has its own Maven Dependency Tree (MDT), which is the transitive closure of all the dependencies needed to build the artifact, as resolved by Maven.

Definition 2 (Maven Dependency Tree)

The MDT of an artifact \(v \in \mathcal {V}\) is a directed acyclic graph of artifacts, with v as the root node, and a set of edges \(\mathcal {E}\) representing dependency relationships between them.

In this work, we introduce the novel concept of bloated dependency as follows:

Definition 3 (Bloated Dependency)

An artifact p is said to have a bloated dependency relationship \(\varepsilon _{b} \in \mathcal {E}\) if there is a path in its MDT, between p and any dependency d of p, such that none of the elements in the API of d are used, directly or indirectly, by p.

To reason about the bloated dependencies of an artifact, we introduce a new data structure, called the Dependency Usage Tree (DUT) as follows.

Definition 4 (Dependency Usage Tree)

The DUT of an artifact a, defined as \(\text {DUT}_{a} = (\mathcal {V}, \mathcal {E}, \mathcal {r})\), is a tree, whose nodes are the same as the Maven Dependency for a and which edges are all of the (a,ai), for all nodes ai ∈DUTa. A labeling function \(\mathcal {r}\) assigns each edge one of the following six dependency usage types: \(\mathcal {r} : \mathcal {E} \rightarrow \{\text {ud, ui, ut, bd, bi, bt}\}\) such that:

$$ \mathcal{r} (\langle p,d\rangle) = \begin{cases} \text{ud}, & \text{if } d \text{ is used and it is directly declared by } p\\ \text{ui}, & \text{if } d \text{ is used and it is inherited from a parent of } p\\ \text{ut}, & \text{if } d \text{ is used and it is resolved transitively by } p\\ \text{bd}, & \text{if } d \text{ is bloated and it is directly declared by } p\\ \text{bi}, & \text{if } d \text{ is bloated and it is inherited from a parent of } p\\ \text{bt}, & \text{if } d \text{ is bloated and it is resolved transitively by } p\\ \end{cases} $$

It is to be noted that, in the case of transitive dependencies, \(\mathcal {r}\) assigns the bt label to the relationship (a,ai) if and only if two conditions hold: 1) a does not use any member of ai, and 2) none of the artifacts in the tree need to use ai to fulfill the requirements of a.

Given a Maven artifact a we build both the MDTa and the DUTa. Both trees include exactly the same set of nodes, but the edges are different. In the MDTa, an edge (a1, a2) exists when the POM of a1 declares a dependency towards a2. In the DUTa, all edges start from a, and an edge (a, a1) means that a1 is an artifact in the MDTa. In the case a1 is not a direct dependency of a, then the edge (a, a1) does not exist in the MDTa, yet we need to model it, since it is the relation (a, a1) that can be bloated or used.

Example

Figure 2 illustrates the dependency usage tree for the example presented in Fig. 1. Analyzing the bytecode of jxls-poi, we find no references to any API member of the direct dependencies commons-jexl (explicitly declared in the POM) and sl4j (inherited from its parent POM). Therefore, these dependency relationships are labelled as bloated-direct (bd) and bloated-inherited (bi) dependencies, respectively.

Fig. 2
figure 2

Dependency Usage Tree (DUT) for the example presented in Fig. 1. Edges are labelled according to Definition 4 to reflect the usage status between jxls-poi and each one of its dependencies

Now let us consider the dependency to commons-codec. In the MDT of jxls-poi (cf. Fig. 1), we observe that commons-codec is a transitive dependency of jxls-poi, through poi. From the perspective of bloat, what we want to know is the following: is commons-codec necessary to build and run jxls-poi? Therefore, we are interested in the relationship between jxls-poi and commons-codec, which we model in the DUT of jxls-poi (cf. Fig. 2). To answer the question of usage we need two analyses. First, an analysis of the bytecode of jxls-poi reveals that it does not use commons-codec directly. Second, we observe that jxls-poi uses some members of poi, and that these members of poi do not use commons-codec. So, we conclude that the relationship between jxls-poi and commons-codec is bloated, and the corresponding edge is labelled bt. It is important to note that a bloated transitive dependency relationship between jxls-poi and commons-codec does not mean that commons-codec is bloated for poi, but only for the subpart of poi that is necessary for jxls-poi. Table 1 summarizes the labelling of all the dependency relationships of jxls-poi.

Table 1 Contingency table of the different types of dependency relationships studied in this work for the example presented in Fig. 2

DepClean: A Tool for Detecting and Removing Bloated Dependencies

For our study, we design and implement a specific tool called DepClean. An overview of DepClean is shown in Fig. 3, it works as follows. It receives as inputs a built Maven project and a repository of artifacts, then it extracts the dependency tree of the projects and constructs a DUT to identify the set of dependencies that are actually used by the project. DepClean has two outputs: (1) it returns a report with the usage status of all types of dependencies, and (2) it produces an alternative version of the POM file (POMd) with all the bloated dependencies removed (i.e., the XML node of the bloated dependency is removed). DepClean does not perform any modifications to the source code, bytecode, or configurations files in the project under consideration.

Fig. 3
figure 3

Overview of the tool DepClean to detect and remove bloated dependencies in Maven projects. Rounded squares represent artifacts, circles inside the artifacts are API members, arrows between API members represents bytecode calls between artifacts, arrows between artifacts represent dependency relationships between them

Algorithm 1 details the main procedure of DepClean. The algorithm receives as input a Maven artifact p that includes a set of dependencies in its dependency tree, denoted as DT, and returns a report of the usage status of its dependencies and a debloated version of its POM. Notice that DepClean computes two transitive closures: over the Maven dependency tree (line 2) and over the call graph of API members (line 3).

figure f

The algorithm first copies the POM file of p, resolving all its direct and transitive dependencies locally, and obtaining the dependency tree (lines 1 and 2). If p is a module of a multi-module project, then all the dependencies declared in its parent POM are included as direct dependencies of p. Then, the algorithm proceeds to construct a set with the dependencies that are actually used by p (line 3).

Algorithm 2 explains the bytecode analysis. The detection component statically analyzes the bytecode of p and all its dependencies to check which API members are being referenced by the artifact, either directly or indirectly. Notice that it behaves differently if the included artifact is a direct, inherited, or a transitive dependency. If none of the API members of a dependency dDT are called, even indirectly via transitive dependencies, then d is considered to be bloated, and we proceed to remove it.

figure g

In Maven, we remove bloated dependencies in two different ways: (1) if the bloated dependency is explicitly declared in the POM, then we remove its declaration clause directly (line 9 in Algorithm 1), or (2) if the bloated dependency is inherited from a parent POM or induced transitively, then we excluded it in the POM (line 11 in Algorithm 1). This exclusion consists in adding an <exclusion> clause inside a direct dependency declaration, with the groupId and artifactId of the transitive dependency to be excluded. Excluded dependencies are not added to the classpath of the artifact by way of the dependency in which the exclusion was declared.

DepClean is implemented in Java as a Maven plugin that extends the maven-dependency-analyzerFootnote 3 tool, which is actively maintained by the Maven team and officially supported by the Apache Software Foundation. For the construction of the dependency tree, DepClean relies on the copy-dependencies and tree goals of the maven-dependency-plugin. Internally, DepClean relies on the ASMFootnote 4 library to visit all the .class files of the compiled projects in order to register bytecode calls towards classes, methods, fields, and annotations among Maven artifacts and their dependencies. For example, it captures all the dynamic invocations created from class literals by parsing the bytecodes in the constant pool of the classes. DepClean defines a customized parser that reads entries in the constant pool of the .class files directly, in case it contains special references that ASM does not support. This allows the plugin to statically capture reflection calls that are based on string literals and concatenations. Compared to maven-dependency-analyzer, DepClean adds the unique features of detecting transitive and inherited bloated dependencies, and to produce a debloated version of the POM file. DepClean is open-source and reusable from Maven Central, the source code is available at https://github.com/castor-software/depclean.

Experimental Methodology

In this section, we present the research questions that articulate our study. We also describe the experimental protocols used to select and analyze Maven artifacts for an assessment of the impact of bloated dependencies in this ecosystem.

Research Questions

Our investigation of bloated dependencies in the Maven ecosystem is composed of four research questions grouped in two parts. In the first part, we perform a large scale quantitative study to answer the following research questions:

  • RQ1: How frequently do bloated dependencies occur? With this research question, we aim at quantifying the amount of bloated dependencies among 9,639 Maven artifacts. We measure direct, inherited and transitive dependencies to provide an in-depth assessment of the dependency bloat in the Maven ecosystem.

  • RQ2: How do the reuse practices affect bloated dependencies? In this research question, we analyze bloated dependencies with respect to two distinctive aspects of reuse in the Maven ecosystem: the additional complexity of the Maven dependency tree caused by transitive dependencies, and the choice of a multi-module architecture.

The second part of our study focuses on 30 notable Maven projects and presents the qualitative feedback about how developers react to bloated dependencies, and to the solutions provided by DepClean. It is guided by the following research questions:

  • RQ3: To what extent are developers willing to remove bloated-direct dependencies? Direct dependencies are those that are explicitly declared in the POM. Hence, those dependencies are easy to remove since it only requires the modification of a POM that developers can easily change. In this research question, we use DepClean to detect and fix bloated-direct dependencies. Then, we communicate the results to the developers. We report on their feedback.

  • RQ4: To what extent are developers willing to exclude bloated-transitive dependencies? Transitive dependencies are those not explicitly declared in the POM but induced from other dependencies. We exchange with developers about such cases. This gives unique insights about how developers react to excluding transitive dependencies from their projects.

Experimental Protocols

Protocol of the Quantitative Study (RQ1 & RQ2)

Figure 4 shows our process to build a dataset of Maven artifacts in order to answer RQ1 and RQ2. Steps and focus on the collection of a representative set, according to the number of direct dependencies, of Maven artifacts: we sample our study subjects from the whole MDG, then we resolve the dependencies of each study subject. In steps and , we analyze dependency usages with DepClean and compute the set of metrics to answer RQ1 and RQ2.

Fig. 4
figure 4

Experimental framework used to collect artifacts and analyze bloated dependencies in the Maven ecosystem

Filter Artifacts

In the first step, we leverage the Maven Dependency Graph (MDG) from previous research (Benelallam et al. 2019), a graph database that captures the complete dependency relationships between artifacts in Maven Central at a given point in time. Figure 5a shows the distribution of the number of direct dependencies of the artifacts with at least one direct dependency in the MDG. The number of direct dependencies is a representative measure that reflects the initial intentions of developers with respect to code reuse. We select a representative sample that includes 14,699 Maven artifacts (Fig. 5b). Representativeness is achieved by sampling over the probability distribution of the number of direct dependencies per artifact in the MDG, per the recommendation of Shull (2007, Chapter 8.3.1). From the sampled artifacts, we select as study subjects all the artifacts that meet the following additional criteria:

  • Public API: The subjects must contain at least one .class file with one or more public methods, i.e., can be reused via external calls to their API.

  • Diverse: The subjects all have different groupId and artifactId, i.e., they belong to different Maven projects.

  • Reused: The subjects are used by at least one client via direct declaration.

  • Complex: The subjects have at least one direct dependency with compile scope, i.e., we can analyze the dependency tree and the reused artifacts.

  • Latest: The subjects are the latest released version at the time of the experiment (October, 2019).

Fig. 5
figure 5

Distribution of the number of direct dependencies of the artifacts at the different stages of the data filtering process

After this systematic selection procedure, we obtain a dataset of 9,770 Maven artifacts. The density of artifacts with a number of direct dependencies in the range [3, 9] in our dataset (Fig. 5c) is higher than in the MDG (Fig. 5a). This is a direct consequence of our selection criteria where artifacts must have at least one direct dependency with compile scope. This filter removes artifacts that contain only dependencies that are not shipped in the JAR of the artifact (e.g., test dependencies). Therefore, the 9,770 artifacts used as study subjects are representative of the artifacts in Maven Central that include third-party dependencies in the JAR.

Resolve Dependencies

In the second step, we download the binaries of all the selected artifacts and their POMs from Maven Central and we resolve all their direct and transitive dependencies to a local repository. To ensure the consistency of our analysis, we discard the artifacts that depend on libraries hosted in external repositories. In case of any other error when downloading some dependency, we exclude the artifact from our analysis. This occurred for a total of 131 artifacts in the dataset obtained in the first step.

Table 2 shows the descriptive statistics about the 9,639 artifacts in our final dataset for RQ1 and RQ2. The dataset includes 44,488 direct, 180,693 inherited, and 498,263 transitive dependency relationships (723,444 in total). We report about their dependencies with compile scope, since those dependencies are necessary to build the artifacts. Columns #C, #M, and #F give the distribution of the number of classes, methods, and fields per artifact (we count both the public and private API members). The size of artifacts varies, from small artifacts with one single class (e.g., org.elasticsearch.client:transport:6.2.4), to large libraries with thousands of classes (e.g., org.apache.hive:hive-exec:3.1.0). In total, we analyze the bytecode of more than 30 millions of API members. Columns #D, #I, and #T account for the distributions of direct, inherited, and transitive dependencies, respectively. com.bbossgroups.pdp:pdp-system:5.0.3.9 is the artifact with the largest number of declared dependencies in our dataset, with 148 dependency declarations in its POM file, while be.atbash.json:octopus-accessors-smart:0.9.1 has the maximum number of transitive dependencies: 1,776. The distributions of direct and transitive dependencies are notably different: typically the number of transitive dependencies is an order of magnitude larger than direct dependencies, with means of 20 and 4, respectively.

Table 2 Descriptive statistics of the 9,639 Maven artifacts selected to conduct our quantitative study of bloated dependencies (RQ1 & RQ2)

Dependency Usage Analysis

This is the first step to answer RQ1 and RQ2: collect the status of all dependency relationships for each artifact in our dataset. For each artifact, we first unpack its JAR file, as well as its dependencies. Then, for each JAR file, we analyze all the bytecode calls to API class members using DepClean. This provides a Dependency Usage Tree (DUT) for each artifact, on which each dependency relationship is labeled with one of the six categories as we illustrated in Table 1: bloated-direct (bd), bloated-inherited (bi), bloated-transitive (bt), used-direct (ud), used-inherited (ui), or used-transitive (ut).

Collect Dependency Usage Metrics

This last step consists of collecting a set of metrics about the global presence of bloated dependencies. We define our analysis metrics with the goals of studying 1) the dependency usage relationships in the DUT (RQ1), and 2) the complexity resulting from the adoption of the multi-module Maven architecture (RQ2).

In RQ1, we analyze our dataset as a whole, looking at the usage status of dependency relationships from two perspectives:

  • Global distribution of dependency usage. This is the normalized distribution of each category of dependency usage, over each dependency relationship of each of the 9,639 artifacts in our dataset.

  • Distribution of dependency usage type, per artifact. For each of the six types of dependency usage, we compute the normalized ratio over the total number of dependency relationships for each artifact in our dataset.

In RQ2, we analyze how the specific reuse strategies of Maven relate to the presence of bloated dependencies. First, we use the number of transitive dependencies and the height of the dependency tree as a measure of complexity. The former measure is guided by the fact that transitive dependencies are more difficult to handle by developers; the latter measure is guided by the idea that dependencies that are deeper in the dependency tree are more likely to be bloated. We use the following metrics:

  • Bloated dependencies w.r.t. the number of transitive dependencies. For each artifact that has at least one transitive dependency, we determine the relation between the ratio of transitive dependencies and the ratio of bloated dependencies.

  • Bloated-transitive dependencies w.r.t. to the height of the dependency tree. Given an artifact and its Maven dependency tree, the height of the tree is the longest path between the root and its leaves. To compute this metric, we group our artifacts according to the height of their tree. The maximum dependency tree height that we observed is 14. However, there are only 152 artifacts with a tree higher than 9. Therefore, we group all artifacts with height ≥ 9. For each subset of artifact with the same height, we compute the size of the subset and the distribution of bloated-transitive dependencies of each artifact in the subset.

Second, we distinguish the presence of bloated dependencies between single and multi-module Maven projects, according to the following metrics:

  • Global distribution of dependency usage in a single or multi-module project. We present two plots that measure the distribution of each type of dependency usage in the set of single and multi-module projects. It is to be noted that the plot for single-module projects does not include bloated-inherited (bi) and used-inherited (ui) dependencies since they have no inherited dependencies.

  • Distribution of dependency usage type, per artifact, in a single or multi-module project. We present two plots that provide six distributions each: the distribution of each type of dependency usage type for artifacts that are in a single-module or multi-module project.

Protocol of the Qualitative Study (RQ3 & RQ4)

In RQ3 and RQ4, we perform a qualitative assessment of the relevance of bloated dependencies for the developers of open-source projects. We systematically select 30 notable open-source projects hosted on GitHub to conduct this analysis. We query the GitHub API to list all the Java projects ordered by their number of stars. Then, we randomly select a set of projects that fulfil all the following criteria: (1) we can build them successfully with Maven, (2) the last commit was at the latest in October 2019, (3) they declare at least one dependency in the POM, (4) they have a description in the README about how to contribute through pull requests, and (5) they have more than 100 stars on GitHub.

Table 3 shows the selected 30 projects per those criteria, to which we submitted at least one pull request. They are listed in decreasing order according to their number of stars on GitHub. The first column shows the name of the project as declared on GitHub, followed by the name of the targeted module if the project is multi-module. Notice that in the case of jenkins we submitted two pull requests targeting two distinct modules: core and cli. Columns two to four describe the projects according to its category as assigned to the corresponding released artifact in Maven Central, the number of commits in the master branch in October 2019, and the number of stars at the moment of conducting this study. Columns five to seven report about the total number of direct, inherited, and transitive dependencies included in the dependency tree of each considered project.

Table 3 Maven projects selected to conduct our qualitative study of bloated dependencies (RQ3 & RQ4)

We answer RQ3 according to the following protocol: 1) we run DepClean, we build the artifact with the debloated POM file, 2) if the project builds successfully, we analyze the project to propose a relevant change to the developers per the contribution guidelines, 3) we propose a change in the POM file in the form of a pull request, and 4) we discuss the pull request through GitHub. Figure 6 shows an excerpt of the diff of such a change in the POM file. We note that the submitted pull requests contain a small modification in a single file: the POM.

Fig. 6
figure 6

Example of commit removing the bloated-direct dependency org.apache.httpcomponents:httpmime in the project Undertow

In the first step of the protocol, we use DepClean to obtain a report about the usage of dependencies. We analyze dependencies with both compile and test scope. Once a bloated-direct dependency is found, we remove it directly in the POM and proceed to build the project. If the project builds successfully after the removal (all the tests pass), we submit the pull request with the corresponding change. If after the removal of the dependency the build fails, then we consider the dependency as used dynamically and do not suggest removing it. In the case of multi-module projects, with bloated dependencies in several modules, we submitted a single pull request per module.

For each pull request, we analyze the Git history of the POM file to determine when the bloated dependency was declared or modified. Our objective is to collect information in order to understand how the dependencies of the projects change during their evolution. This allows us to prepare a more informative pull request message and to support our discussion with developers. We also report on the benefits of tackling these bloated dependencies by describing the differences between the original and the debloated packaged artifact of the project in terms of the size of the bundle and the complexity of its dependency tree, when the difference was significant. Each pull request includes an explanatory message. Figure 7 shows an example of the pull request message submitted to the project Undertow.Footnote 5 The message explains the motivations of the proposed change, as well as the negative impact of keeping these bloated dependencies in the project.

Fig. 7
figure 7

Example of message of a pull request sent to the project Undertow on GitHub

To answer RQ4, we follow the same pull request submission protocol as for RQ3. We use DepClean to detect bloated-transitive dependencies and submit pull requests suggesting the addition of the corresponding exclusion clauses in each project POM. Figure 8 shows an example of a pull request message submitted to the project Apache AccumuloFootnote 6, while Fig. 9 shows an excerpt of the commit proposing the exclusion of the transitive dependency org.apache.httpcomponents:httpcore from the direct dependency org.apache.thrift:libthrift in its POM.

Fig. 8
figure 8

Example of message in a pull request sent to the project Apache Accumulo on GitHub

Fig. 9
figure 9

Example of commit excluding the bloated-transitive dependency org.apache.httpcomponents:httpcore in the project Apache Accumulo

Additional information related to the selected projects and the research methodology employed is publicly available as part of our replication package at https://github.com/castor-software/depclean-experiments.

Experimental Results

We now present the results of our in-depth analysis of bloated dependencies in the Maven ecosystem.

RQ1: How Frequently do Bloated Dependencies Occur?

In this first research question, we investigate the status of all the dependency relationships of the 9,639 Maven artifacts under study.

Figure 10 shows the overall status of the 723,444 dependency relationships in our dataset. The x-axis represents the percentages, per usage type, of all the dependencies considered in the studied artifacts. The first observation is that the bloat phenomenon is massive: 543,610 (75.1%) of all dependencies are bloated, they are not needed to compile and run the code. This bloat is divided into three separate categories: 19,673 (2.7%) are bloated-direct dependency relationships (explicitly declared in the POMs); 111,649 (15.4%) are bloated-inherited dependency relationships from parent module(s); and 412,288 (57%) are bloated-transitive dependencies. Figure 10 shows that 75.1% of the relationships (edges in the dependency usage tree) are bloated dependencies. Note that this observation does not mean that 543,610 artifacts are unnecessary and can be removed from Maven Central. The same artifact can be present in several DUTs, i.e., reused by different artifacts, but be part of a bloated dependency relationship only in some of these DUTs, and part of a used relationship in the other DUTs.

Fig. 10
figure 10

Ratio per usage status of the 723,444 dependency relationships analyzed. Raw counts are inside parentheses below each percentage

Figure 11 shows the overall status of the dependencies with respect to the type of the dependency relationship (direct, inherited, and transitive). We observe that approximately 1/3 of direct dependencies are bloated (34.23%), whereas inherited and transitive dependencies have a higher percentage of bloat (61.79% and 82.5% of bloat, respectively). These results indicate that artifacts with inherited and transitive dependencies are more likely to have more bloated dependencies. They also confirm that transitive dependencies are the most susceptible to bloat.

Fig. 11
figure 11

Ratio per dependency type of bloated and used dependencies of the 723,444 dependency relationships analyzed

In the following, we illustrate the three types of bloated dependency relationships with concrete examples.

Bloated-Direct

We found that 2.7% of the dependencies declared in the POM file of the studied artifacts are not used at all via bytecode calls. Recall that detecting this type of bloated dependencies is good, because they are easy to remove by developers with a single change in the POM file of the project under consideration. As an example, the Apache IgniteFootnote 7 project has deployed an artifact: org.apache.ignite:ignite-zookeeper:2.4.0, which contains only one class in its bytecode: TcpDiscoveryZookeeperIpFinder, and it declares a direct dependency in the POM towards slf4j, a widely used Java logging library. However, if we analyze the bytecode of ignite-zookeeper, no call to any API member of sl4j exists, and therefore, it is a bloated-direct dependency. After a manual inspection of the commit history of the POM, we found that sl4j was extensively used across all the modules of Apache Ignite at the early stages of the project, but it was later replaced by a dedicated logger, and its declaration remained intact in the POM.

Bloated-Inherited

In our dataset, a total of 4,963 artifacts are part of multi-module Maven projects. Each of these artifacts declares a set of dependencies in its POM file, and also inherits a set of dependencies from a parent POM. DepClean marks those inherited dependencies are either bloated-inherited or used-inherited. Our dataset includes a total of 111,649 dependency relationships labeled as bloated-inherited, which represents 15.4% of all dependencies under study and 61.8% of the total of inherited dependencies. For example, the artifact org.apache.drill:drill-protocol:1.14.0 inherits dependencies commons-codec and commons-io from its parent POM org.apache.drill:drill-root:1.14.0, however, those dependencies are not used in this module, and therefore they are bloated-inherited dependencies.

Listing 2
figure l

Code snippet of the class VerbDefinitionDropFilter present in the artifact org.apache.streams:streams-filters:0.6.0. The library com.google.guava:guava:20.0 is included in its classpath via transitive dependency and called from the source code, but no dependency towards guava is declared in its POM

Bloated-Transitive

In our dataset, bloated-transitive dependencies represent the majority of the bloated dependency relationships: 412,288 (57%). This type of bloat is a natural consequence of the Maven dependency resolution mechanism, which automatically resolves all the dependencies whether they are explicitly declared in the POM file of the project or not. Transitive dependencies are the most common type of dependency relationships, having a direct impact on the growth of the dependency trees. This type of bloat is the most common in the Maven ecosystem. For example, the artifact org.eclipse.milo:sdk-client:0.2.1 ships the transitive dependency gson in its MDT, induced from its direct dependency towards bsd-parser-core. However, the part of bsd-parser-core used by sdk-client does not call any API member of gson, and therefore it is a bloated-transitive dependency.

In the following, we discuss the dependencies that are actually used. We observe that direct dependencies represent only 3.4% of the total of dependencies in our dataset. This means that the majority of the dependencies that are necessary to build Maven artifacts are not declared explicitly in the POM files of these artifacts.

It is interesting to note that 85,975 of the dependencies used by the artifacts under study are transitive dependencies. This kind of dependency usage occurs in two different scenarios: (1) the artifact uses API members of some transitive dependencies, without declaring them in its own POM file; or (2) the transitive dependency is necessary to provide a functionality to another, actually used dependency, in the dependency tree of the artifact.

Listing 3
figure m

Code snippet of the class AuditTask present in the artifact org.duracloud:auditor:4.4.3. The library org.codehaus.jackson:jackson-mapper-asl:1.6.2 is used indirectly through the direct dependency org.duracloud:common-json:4.4.3

We now discuss an example of the first scenario based on the org.apache.streams:streams-filters:0.6.0 artifact from the Apache StreamsFootnote 8 project. It contains two classes: VerbDefinitionDropFilter and VerbDefinitionKeepFilter. Listing 2 shows part of the source code of the class VerbDefinitionDropFilter, which imports the class PreCondition from library guava (line 2) and uses its static method checkArgument in line 8 of method process. However, if we inspect the POM of streams-filters, we notice that there is no dependency declaration towards guava. It declares a dependency towards streams-core, which in turn depends on the streams-utils artifact that has a direct dependency towards guava. Hence, guava is a used-transitive dependency of streams-filters, called from its source code.

Let us now present an example of the second scenario. Listing 3 shows an excerpt of the class AuditTask included in the artifact org.duracloud:auditor:4.4.3, from the project DuraCloud.Footnote 9 In line 6, the method getPropsSerializer instantiates the JaxbJsonSerializer object that belongs to the direct dependency org.duracloud:common-json:4.4.3. This object, in turn, creates an ObjectMapper from the transitive dependency jackson-mapper-asl. Hence, jackson-mapper-asl is a necessary, transitive provider for org.duracloud:auditor:4.4.3.

Figure 12 shows the distributions of dependency usage types per artifact. The figure presents superimposed log-scaled box-plots and violin-plots of the number of dependency relationships corresponding to the six usage types studied. Box-plots indicate the standard statistics of the distribution (i.e., lower/upper inter-quartile range, max/min values, and outliers), while violin plots indicate the entire distribution of the data.

Fig. 12
figure 12

Distributions of the six types of dependency usage relationships for the studied artifacts. The thicker areas on each curve represent concentrations of artifacts per type of usage

We observe that the distributions of the bloated-direct (bd) and bloated-transitive (bt) dependencies vary greatly. Bloated-direct dependencies are distributed between 0 and 1 (1st-Q and 3rd-Q), with a median of 0; whereas the second ranges between 2 and 41 (1st-Q and 3rd-Q), with a median of 11. These values are in line with the statistics presented in Table 2, since the number of direct and transitive dependencies in general differ approximately by one order of magnitude. Overall, from the 9,639 Maven artifacts studied, 3,472 (36%) have at least one bloated-direct dependency, while 8,305 (86.2%) have at least one bloated-transitive.

On the other hand, the inter-quartile range of bloated-direct (bd) dependencies is more compact than the used-direct (ud). In other words, the dependencies declared in the POM are mostly used. This result is expected, since developers have more control over the edition (adding/removing dependencies) of the POM file of their artifact.

The median number of used-transitive (ut) dependencies is significantly lower than the median number of bloated-transitive (bt) dependencies (2, vs. 11). This suggests that the default dependency resolution mechanism of Maven is suboptimal with respect to ensuring minimal dependency inclusion.

The number of outliers in the box-plots differs for each usage type. Notably, the bloated-direct dependencies have more outliers (in total, 25 artifacts have more than 100 bloated-direct dependencies). In particular, the artifact com.bbossgroups.pdp:pdp-system:5.0.3.9 has the maximum number of bloated-direct dependencies: 133, out of the 147 declared in its POM. The total number of artifacts with at least one bloated-direct dependency in our dataset is 2,298, which represents 23.8% of the 9,639 studied artifacts.

figure n

RQ2: How do the Reuse Practices Affect Bloated Dependencies?

In this research question, we investigate how the reuse practices that lead to these distinct types of dependency relationships are related to the bloated dependencies that emerge in Maven artifacts.

Figure 13 shows the distributions, in percentages, of the direct, inherited, and transitive dependencies for the 9,639 studied artifacts. The artifacts are sorted, from left to right, in increasing order according to their ratio of direct dependencies. The y-axis indicates the ratio of each type of dependency for a given artifact. First, we observe that 4,967 artifacts belong to multi-module projects. Among these artifacts, the extreme case (far left of the plot) is org.janusgraph:janusgraph-berkeleyje:0.4.0, which declares only 1.4% of its dependencies in its POM, while the 48.6% of its dependencies are inherited from parent POM files, and 50% are transitive. Second, we observe that the ratio of transitive dependencies is not equally distributed. On the right side of the plot, 879 (9.1%) artifacts have no transitive dependency (they have 100% direct dependencies). Meanwhile, 5,561 (57.7%) artifacts have more than 50% transitive dependencies. The extreme case is org.apereo.cas:cas-server-core-api-validation:6.1.0, with 77.6% transitive dependencies.

Fig. 13
figure 13

Distribution of the percentages of direct, inherited, and transitive dependencies for the 9,639 artifacts considered in this study

In summary, the plot in Fig. 13 offers a big picture of the distribution of the three types of dependency usage in our dataset. The inherited and transitive dependencies are a significant phenomenon in Maven: 8,742 (90.7%) artifacts in our dataset have transitive dependencies, and 51.5% of artifacts belong to multi-module projects. This observation confirms the results of the previous section, most of the bloated dependencies in our dataset are either transitive (57%) or inherited (15.4%).

Transitive Dependencies

Figure 14 plots the relation between the ratio of transitive dependencies and the ratio of bloated dependencies. Each dot represents an artifact. Dots have a higher opacity in the presence of overlaps.

Fig. 14
figure 14

Relation between the percentages of transitive dependencies and the percentage of bloated dependencies in the 9,639 studied artifacts

The key insight in Fig. 14 is that the larger concentration of artifacts is skewed to the top right corner, indicating that artifacts with a high percentage of transitive dependencies also tend to exhibit higher percentages of bloated dependencies. Indeed, both variables are positively correlated, according to the Spearman’s rank correlation test (ρ = 0.65, p-value < 0.01).

Figure 15 shows the distribution of the ratio of transitive bloated dependencies according to the height of the dependency tree. The artifact in our dataset with the largest height is top.wboost:common-base-spring-boot-starter:3.0.RELEASE, with a height of 14. The bar plot on top of Fig. 15 indicates the number of artifacts that have the same height. We observe that most of the artifacts have a height of 4: 2,226 artifacts in total. Considering the number of dependencies, this suggests that the dependency trees tend to be wider than deep. This is direct consequence of the automatic dependency management by Maven: any dependency that already appears at a level closer to the root will be omitted by Maven if it is referred to at a deeper level.

Fig. 15
figure 15

Distribution of the percentages of bloated-transitive dependencies for our study subjects with respect to the height of the dependency trees. Height values greater than 10 are aggregated. The bar plot at the top represents the number of study subjects for each height

Looking at the 58 artifacts with height ≥ 9, we notice that most of them belong to multi-module projects, and declare other modules in the same project as their direct dependencies. This is a regular practice of multi-module projects, which allows to release each module as an independent artifact. Meanwhile, this increases the complexity of dependency trees. For example, artifact org.wso2.carbon.devicemgt:org.wso2.carbon.apimgt.handlers:3.0.192 is the extreme case of this practice in our dataset, with two direct dependencies towards other modules of the same project that in turn depend on other modules of this project. As a result, this artifact has 342 bloated-transitive and 87 bloated-inherited dependencies, a dependency tree of height 11, and is part of a multi-module project with a total of 79 modules released in Maven Central.

The plot in Fig. 15 shows a clear increasing trend of bloated-transitive dependencies as the height of the dependency tree increases. Indeed, both variables are positively correlated, according to the Spearman’s rank correlation test (ρ = 0.54, p-value < 0.01). For artifacts with a dependency tree of height greater than 9, at least 28% of their transitive dependencies are bloated, while the median of the percentages of bloated-transitive dependencies for artifacts with height larger than 5 is more than 50%.

This finding confirms and complements the results of Fig. 14, showing that the height of the dependency tree is directly related to the occurrence of bloat. However, the height of the tree may not be the only factor that causes the bloat. For example, we hypothesize that number of transitive dependencies is another essential factor.

In order to validate this hypothesis, we perform a Spearman’s rank correlation test between the number of bloated-transitive dependencies and the size of the dependency tree, i.e., the number of nodes in each tree. We found that there is a significant positive correlation between both variables (ρ = 0.67, p-value < 0.01). This confirms that the actual usage of transitive dependencies decreases with the increasing complexity of the dependency tree. This result is aligned with our previous study that suggest that most of the public API members of transitive dependencies are not used by its clients (Harrand et al. 2019).

In summary, our results point to the excess of transitive dependencies as one of the fundamental causes of the existence of bloated dependencies in the Maven ecosystem.

Single-Module vs. Multi-Module

Let us investigate on the differences between single and multi-module architectures with respect to the presence of bloated dependencies. Figure 16 compares the distributions of bloated and used dependencies between multi-module and single-module artifacts in our dataset. We notice that, in general, multi-module artifacts have slightly more bloat than single-module, precisely 3.1% more (the percentage of bloat in single-module is 5.8% + 67.3% = 73.1% vs. 0.9% + 24.2% + 51.1% = 76.2% in multi-module). More interestingly, we observe that a majority of the inherited dependencies are bloated: 24.2% of the dependencies among multi-module project are bloated-inherited (bi), while only 15% are used-inherited (ui). This suggests that most of the dependencies inherited by Maven artifacts that belong to multi-module artifacts are not used by these modules.

Fig. 16
figure 16

Comparison between multi-module and single-module artifacts according to the percentage status of their dependency relationships. Raw counts are inside parentheses below each percentage

We observe that the percentage of bloated-direct dependencies in multi-module artifacts is very small (0.9%) in comparison with single-module (5.8%). Meanwhile, the percentage of bloated-transitive dependencies in single-module (67.3%) is larger than in multi-module (51.1%). This is due to the “shift” of a part of direct and transitive dependencies into inherited dependencies when using a parent POM. Indeed, the “shift” from direct to inherited is the main motivation for having a parent POM: to have one single declaration of dependencies for many artifacts instead of letting each artifact manage their own dependencies.

This “shift” in the nature of dependencies between single and multi-module artifacts is further emphasized in Fig. 17. This plot shows superimposed log scaled box-plots and violin-plots comparing the distributions of the number of distinct dependency usage types per artifact, for single-module (top part of the figure) and multi-module (bottom part).

Fig. 17
figure 17

Comparison between multi-module and single-module projects according to their distributions of dependency usage relationships

We observe that multi-module artifacts have less bloated-direct (1st-Q = 0, median = 0, 3rd-Q = 0) and less bloated-transitive (1st-Q = 2, median = 9, 3rd-Q = 40), compared to single-modules, as shown in Fig. 17. However, multi-module artifacts have a considerably larger number of bloated-inherited dependencies instead (1st-Q = 1, median = 5, 3rd-Q = 20). The extreme case in our dataset is the artifact co.cask.cdap:cdap-standalone:4.3.4, with 326 bloated-inherited dependencies in total.

In summary, the multi-module architecture in Maven projects contributes to limit redundant dependencies and facilitates the consistent versioning of dependencies in large projects. However, it introduces two challenges for developers. First, it leads to the emergence of bloated-inherited dependencies because of the friction of maintaining a common parent POM file: it is more difficult to remove dependencies from a parent POM than from an artifact’s own POM. Second, it is more difficult for developers to be aware of and understand the dependencies that are inherited from the parent POM. This calls for better tooling and user interfaces to help developer grasp and analyze the inherited dependencies in multi-module projects, in order to detect bloated dependencies. To our knowledge, this type of tools is absent in the current Java dependency management ecosystem.

figure o

RQ3: To what Extent are Developers Willing to Remove Bloated-Direct Dependencies?

In this research question, our goal is to see how developers react when made aware of bloated-direct dependencies in their projects. We do this by proposing the removal of bloated-direct dependencies to lead developers of mature open-source projects, as described in Section 4.2.2.

Table 4 shows the list of 19 pull requests submitted. Each pull request proposes the removal of at least one bloated-direct dependency in the POM. We received response from developers for 17 pull request. The first and second columns in the table show the name of the project and the pull request on GitHub. Columns three and four represent the number of bloated dependencies removed in the POM and the total number of dependencies removed from the dependency tree with the proposed change, including transitive ones. The last column shows the status of the pull request ( accepted, accepted with changes, rejected, or pending). The last row represent the acceptance rate calculated with respect to the projects with response, i.e., the total number of dependencies removed divided by the number of proposed removals. For example, for project undertow we proposed the removal of 6 bloated dependencies in its module benchmarks. As a result of this change, 17 transitive dependencies were removed from the dependency tree the module.

Table 4 List of pull requests proposing the removal of bloated-direct dependencies created for our experiments

Overall, from the pull requests with responses from developers, 16/17 were accepted and merged. In total, 75 dependencies were removed from the dependency trees of the projects. This result demonstrates the relevance of handling bloated-direct dependencies for developers, and the practical usefulness of DepClean.

Let us now summarize the developer feedback. First, all developers agreed on the importance of refining the projects’ POMs. This is reflected in the positive comments received. Second, their quick responses suggest that it is easy for them to understand the issues associated with the presence of bloated-direct dependencies in their projects. In 8/17 projects, the response time was less than 24 hours, which is an evidence that developers consider this type of improvement as a priority.

Our results also provide evidence of the fact that we, as external contributors to those projects, were able to identify the problem and propose a solution using DepClean. In the following, we discuss four cases of pull requests that are particularly interesting and the feedback provided by developers.

Jenkins

DepClean detects that jtidy and commons-codec are bloated-direct dependencies present in the modules core and cli of jenkins. jtidy is an HTML syntax checker and pretty printer. commons-codec is an Apache library that provides an API to encode/decode from various formats such as Base64 and Hexadecimal.

Developers were reluctant to remove jtidy due to their concerns of affecting the users of jenkins, which could be potential consumers of this dependency. After further inspection, they found that the class HTMLParser of the nis-notification-lamp-pluginFootnote 10 project relies on jtidy transitively for performing HTML parsing.

Developers also pointed out the fact that there is no classloader isolation in jenkins, and hence all dependencies in its core module automatically become part of its public API. A developer also referred to issues related to past experiences removing unused dependencies. He argued that external projects have depended on that inclusion and their builds were broken by such a removal. For example, the Git client plugin of jenkins mistakenly included Java classes from certain Apache authentication library. When they removed the dependency, some downstream consumers of the library were affected, and they had to include the dependency directly.

Consequently, we received the following feedback from an experienced developer of jenkins:

We’re not precluded from removing an unused dependency, but I think that the project values compatibility more than removal of unused dependencies, so we need to be careful that removal of an unused dependency does not cause a more severe problem than it solves.

After some discussions, developers agreed with the removal of commons-codec in module cli. Our pull request was edited by the developers and merged to the master branch one month after.

Checkstyle

DepClean identifies the direct dependency junit-jupiter-engine as bloated. This is a test scope dependency that was added to the POM of checkstyle when migrating integration tests to JUnit 5. The inclusion of this dependency was necessary due to the deprecation of junit-platform-surefire-provider in the Surefire Maven plugin. However, the report of DepClean about this bloated-direct dependency was a false positive. The reason for this output occurs because junit-jupiter-engine is commonly used through reflective calls that cannot be captured at the bytecode level.

Althoughthis pull request was rejected, developers expressed interest in DepClean, which is encouraging. They also proposed a list of features for the improvement of our tool. For example, the addition of an exclusion list in the configuration of DepClean for dependencies that are known to be used dynamically, improvements on the readability of the generated report, and the possibility of causing the build process to fail in case of detecting the presence of any bloated dependency. We implemented each of the requested functionalities in DepClean. As a result, developers opened an issue to integrate DepClean in the Continuous Integration (CI) pipeline of checkstyle, in order to automatically manage their bloated dependencies.Footnote 11

Alluxio

DepClean detects that the direct dependency grpc-netty, declared in the module alluxio-core-transport is bloated. Figure 18 shows that this dependency also induces a total of 10 transitive dependencies that are not used (4 of them are omitted by Maven due to their duplication in the dependency tree). Developers accepted our pull request and also manifested their interest on using DepClean for managing unused dependencies in the future.

Fig. 18
figure 18

Transitive dependencies induced by the bloated-direct dependency grpc-netty in the dependency tree of module alluxio-core-transport. The tree is obtained with the dependency:tree Maven goal

Undertow

DepClean detects a total of 6 bloated-direct dependencies in the benchmarks module of the project undertow: undertow-servlet, undertow-websockets-jsr, jboss-logging-processor, xnio-nio, jmh-generator-annprocess, and httpmime. In this case, we received a rapid positive response from the developers two days after the submission of the pull request. Removing the suggested bloated-direct dependencies has a significant impact on the size of the packaged JAR artifact of the undertow-benchmarks module. We compare the sizes of the bundled JAR before and after the removal of those dependencies: the binary size reduction represents more than 1MB. It is worth mentioning that this change also reduced the complexity of the dependency tree of the module.

figure aq

RQ4: To what Extent are Developers Willing to Exclude Bloated-Transitive Dependencies?

In this research question, our goal is to see how developers react when made aware of bloated-transitive dependencies. We do this by proposing the exclusion of bloated-transitive dependencies to them, as described in Section 4.2.2.

Table 5 shows the list of 13 pull requests submitted. Each pull request proposes the exclusion of at least one transitive dependency in the POM. We received response from developers for 9 pull requests. The first and second columns show the name of the project and the pull request on GitHub. Columns three and four represent the number of bloated-transitive dependencies explicitly excluded and the total number of dependencies removed in the dependency tree as resulting from the exclusion. The last column shows the status of the pull request ( accepted, rejected, or pending). The last row represents the acceptance rate with respect to the projects with response. For example, for the project spoon we propose the exclusion of four bloated-transitive dependencies in its core module. As a result of this change, 31 transitive dependencies were removed from the dependency tree of this module.

Table 5 List of pull requests proposing the exclusion of bloated-transitive dependencies

Overall, from the pull requests with responses from developers, 5 were accepted and 4 were rejected. In total, 65 bloated dependencies were removed from the dependency trees of 5 projects. We notice that the accepted pull requests involve those projects for which the exclusion of transitive dependencies also represents the removal of a large number of other dependencies from the dependency tree. This result suggests that developers are more careful concerning this type of contribution.

As in RQ3, we obtained valuable feedback from developers about the pros and cons of excluding bloated-transitive dependencies. In the following, we provide unique qualitative insights about the most interesting cases and explain the feedback obtained from developers to the research community.

Jenkins

DepClean detects the bloated-transitive dependencies constant-pool-scanner and eddsa in the module core of jenkins. These bloated dependencies were induced through the direct dependencies remoting and cli, respectively. In the message of the pull request, we explain how their exclusion contributes to make the core of jenkins slimmer and its dependency tree clearer.

Although both dependencies were confirmed as unused in the core module of jenkins, developers rejected our pull request. They argue that excluding such dependencies has no valuable repercussion for the project and might actually affect its clients, which is correct. For example, constant-pool-scanner is used by external components, e.g., the class RemoteClassLoader in the remotingFootnote 12 project relies on this library to inspect the bytecode of remote dependencies.

As shown in the following quote from an experienced developer of Jenkins, there is a consensus on the usefulness of removing bloated dependencies, but developers need strong facts to support the removal of transitive dependencies:

Dependency removals and exclusions are really useful, but my recommendation would be to avoid them if there is no substantial gain.

Auto

DepClean reports on the bloated-transitive dependencies listenablefuture and auto-value-annotations in module auto-common of the Google auto project. We proposed the exclusion of these dependencies and submitted a pull request with the POM change.

Developers express several concerns related to the exclusion of these dependencies. For example, a developer believes that it is not worth maintaining exclusion lists for dependencies that cause no problem. They point out that although listenableFuture is a single class file dependency, its presence in the dependency tree is vital to the project, since it overrides the version of the guava library that have many classes. Therefore, the inclusion of this dependency is a strategy followed by guava to narrow the access to the interface ListenableFuture and not to the whole library.Footnote 13

On the other hand, developers agree that auto-value-annotations is bloated. However, they keep it, arguing that it is a test-only dependency, and they prefer to keep annotation-only dependencies and let end users exclude them when desired.

The response from developers suggests that bloated dependencies with test scope are perceived as less harmful. This is reasonable since test dependencies are only available during the test, compilation, and execution phases and are not shipped transitively in the JAR of the artifact. However, we believe that although it is a developers’ decision whether they keep this type of bloated dependency or not, the removal of testing dependencies is regularly a desirable refactoring improvement.

Moshi

DepClean detects that the bloated-transitive dependency kotlin-stdlib-common is present in the dependency tree of modules moshi-kotlin, moshi-kotlin-codegen, and moshi-kotlin-tests of project moshi. This dependency is induced from a common dependency of these modules: kotlin-stdlib.

Developers rejected our pull requests, arguing that excluding such transitive dependency prevents the artifacts from participating in the proper dependency resolution of their clients. They suggest that clients interested in reducing the size of their projects can use specialized shrinking tools, such as ProGuard,Footnote 14 for this purpose.

Although the argument of developers is valid, we believe that delegating the task of bloat removal to their library clients imposes an unnecessary burden on them. On the other hand, recent studies reveal that library clients do not widely adopt the usage of dependency analysis tools for quality analysis purposes (Nguyen et al. 2020).

Spoon

DepClean detects that the transitive dependencies org.eclipse.core.resources, org.eclipse.core.runtime, org.eclipse.core.filesystem, and org.eclipse.text org.eclipse.jdt.core are bloated. All of these transitive dependencies were induced by the inclusion of the direct dependency org.eclipse.jdt.core, declared in the POM of core module of the spoon library.

Table 6 shows how the exclusion of these bloated-transitive dependencies has a positive impact on the size and the number of classes of the library. As we can see, by excluding these dependencies the size of the jar-with-dependencies of the core module of spoon is trimmed from 16.2MB to 12.7MB, which represents a significant reduction in size of 27.6%. After considering this improvements, the developers confirmed the relevance of this change and merged our pull request into the master branch of the project.

Table 6 Comparison of the size and number of classes in the bundled JAR of the core module of spoon, before and after the exclusion of bloated-transitive dependencies

Accumulo

DepClean detects the bloated-transitive dependencies listenablefuture, httpcore and netty in the core module of Apache accumulo. These dependencies were confirmed as bloated by the developers. However, they manifested their concerns regarding their exclusion, as expressed in the following comment:

I’m not sure I want us to take on the task of maintaining an exclusion set of transitive dependencies from all our deps POMs, because those can change over time, and we can’t always know which transitive dependencies are needed by our dependencies.

After the discussion, developers decided to accept and merge the pull request. Overall, developers considered that the proposal is a good idea. They suggest that it would be better to approach the communities of each of the direct dependencies that they use, and encourage them to mark those dependencies as optional, thus they would not be automatically inherited by their users.

Para

DepClean detects the bloated-transitive dependency flexmark-jira-converter. This dependency is induced through the direct dependency flexmark-ext-emoji, declared in the core module of the para project. Our further investigation on the Maven dependency tree of this module revealed that this bloated dependency adds a total of 19 additional dependencies to the dependency tree of the project, of which 15 are detected as duplicated by Maven.

Because of this large number (19) of bloated-transitive dependencies removed, developers accepted the pull request and merged the change into the master branch of the project the same day of the pull request submission.

figure bk

Discussion

In this section, we discuss the implications of our findings and the threats to the validity of the results obtained.

Implications of Results

Our results indicate that most of the dependency bloat is due to transitive dependencies and the Maven dependency inheritance mechanism. This suggests that the Maven dependency resolution strategy, which always picks the dependency that is closer to the root of the tree, may not be the best selection criterion for minimizing transitive dependency bloat. The official Maven dependency management guidelinesFootnote 15 encourage developers to take control over the dependency resolution process via explicit declaration of dependencies in the POM file. This is a good practice to provide better documentation for the project and to keep one’s artifact dependencies independent of the choices of other libraries down the dependency tree. Dependencies declared in this way have priority over the Maven mediation mechanism, allowing developers to have a clear knowledge about which library version they are expecting to be used through transitive dependencies. However, since backward compatibility is not always guaranteed, having fixed transitive dependency versions, and therefore non-declared dependencies, still remains as a widely accepted practice. In this context, the introduction of the module construct in Java 9 provides a higher level of aggregation above packages. This new language element, if largely adopted, may help to reduce the transitive explosion of dependencies. Indeed, this mechanism enables developers to fine tune public access restrictions of API members, explicitly declaring what set of functionalities a module can expose to other modules. This leads to two benefits: (1) it enables reuse declaration at a finer grain than dependencies, and (2) it makes the debloat techniques described in this work safer as it constrains reflection to white-listed modules.

Our results show that even notable open-source projects, which are maintained by development communities with strict development rules, are affected by dependency bloat. Developers confirmed and removed most of the reported bloated-direct dependencies detected by DepClean. However, they are more careful about excluding bloated-transitive dependencies. The addition of exclusion clauses to the POM files is perceived by some developers as an unnecessary maintainability burden. Interestingly, our quantitative results indicate that bloated-transitive dependency relationships represent the largest portion of bloated dependencies, yet, our qualitative study reveals that these bloated relationships are also the ones that developers find the most challenging to handle and reason about. Overall, this work opens the door to new research opportunities on debloating POMs and other build files.

Threats to Validity

In the following, we discuss construct, internal and external threats to the validity of our study.

Construct Validity

The threats to construct validity are related to the novel concept of bloated dependencies and the metrics utilized for its measurement. For example, the DUT constructed by DepClean could be incomplete due to issues during the resolution of the dependencies. We mitigate this threat by building DepClean on top of Maven plugins to collect the information about the dependency relationships. We also exclude from the study those artifacts for which we were unable to retrieve the full dependency usage information.

It is possible that developers repackage a library as a bundle JAR file along with its dependencies, or copy the source code of dependencies directly into their source code, in order to avoid dependency related issues. Consequently, DepClean will miss such dependencies, as they are not explicitly declared in the POM file. Thus, the analysis of dependencies can underestimate the part of bloated dependencies. However, considering the size of our dataset and the feedback obtained from actively maintained projects, we believe that these corner cases do not affect our main results.

Internal Validity

The threats to internal validity are related to the effectiveness of DepClean to detect bloated dependencies. The dynamic features of the Java programming language, e.g., reflection or dynamic class loading present particular challenges for any automatic analysis of Java source code (Landman et al. 2017; Lindholm et al. 2014). Since DepClean statically analyzes bytecode, anything that does not get into the bytecode is not detected (e.g., constants, annotations with source-only retention police, links in Javadocs), which can lead to false positives. To mitigate this threat, DepClean can detect classes or class members that are created or invoked dynamically using basic constructs such as class.forName("someClass") or class.getMethod("someMethod", null).

To evaluate the impact of this limitation in practice, we ran DepClean on 10 additional popular projects. The experiment consists in running the test suite of the projects with the debloated version of the POM files, i.e., relying on dynamic analysis as a validation mechanism. Table 7 shows the results obtained after running the test suite of the version of the project without bloated dependencies. The first column shows the URL of the project on GitHub, the second and third columns represent the number bloated-direct and bloated-transitive dependencies detected by DepClean, and the fourth column is the result of the test ( pass, or fail). As we observe, 9/10 projects pass the test suite, and only one project fails: raft-java. We found that the reason of the failure was the dependency org.projectlombok:lombok:1.18.4, which heavily relies on reflection and other dynamic mechanisms of Java. To prevent the occurrence of false positives, the users of DepClean can add dependencies that are known to be used only dynamically to an exclusion list. Once added this dependency to the exclusion list of DepClean, it is not considered as bloated, and all the tests pass with the other bloated dependencies removed.

Table 7 Evaluation of the results of DepClean by checking if all the test pass after the removal of bloated dependencies

External Validity

The relevance of our findings in other software ecosystems is one threat to external validity. Our observations about bloated dependencies are based on Java and the Maven ecosystem and our findings are restricted to this scope. More studies on other dependency management systems are needed to figure out whether our findings can be generalized. Another external threat relates to the representativeness of the projects considered for the qualitative study. To mitigate this threat, we submitted pull requests to a set of diverse, mature, and popular open-source Java projects that belong to distinct communities and cover various application domains. This means that we contributed to improving the dependency management of projects that are arguably among the best of the open-source Java world, which aims to get as strong external validity as possible.

Related Work

In this work, we propose the first systematic large-scale analysis of bloat in the Maven ecosystem. Here, we discuss the related works in the areas of software debloating and dependency management.

Analysis and Mitigation of Software Bloat

Previous studies have shown that software tends to grow over time, whether or not there is a need for it (Holzmann 2015; Quach et al. 2017). Consequently, software bloat appears as a result of the natural increase of software complexity, e.g., the addition of non-essential features to programs (Brooks 1987). This phenomenon comes with several risks: it makes software harder to understand and maintain, increases the attack surface, and degrades the overall performance. Our paper contributes to the analysis and mitigation of a novel type of software bloat: bloated dependencies.

Celik et al. (2016) presented Molly, a build system to lazily retrieve dependencies in CI environments and reduce build time. For the studied projects, the build time speed-up reaches 45% on average compared to Maven. DepClean operates differently than Molly: it is not an alternative to Maven as Molly is, but a static analysis tool that allows Maven users to have a better understanding and control about their dependencies.

Yu et al. (2003) investigated the presence of unnecessary dependencies in header files of large C projects. Their goal was to reduce build time. They proposed a graph-based algorithm to statically remove unused code from applications. Their results show a reduction of build time of 89.70% for incremental builds, and of 26.38% for fresh builds. Our work does not focus on build performance, we analyze the pervasiveness of dependency bloat across a vast and modern ecosystem of Maven packages.

In recent years, there has been a notable interest in the development of debloating techniques for program specialization. The aim is to produce a smaller, specialized version of programs that consume fewer resources while hardening security (Azad et al. 2019). They range from debloating command line programs written in C (Sharif et al. 2018), to the specialization of JavaScript frameworks (Vázquez et al. 2019) and fully fledged containerized platforms (Rastogi et al. 2017). Most debloating techniques are built upon static analysis and are conservative in the sense that they focus on trimming unreachable code (Jiang et al. 2016), others are more aggressive and utilize advanced dynamic analysis techniques to remove potentially reachable code (Heath et al. 2019). Our work addresses the same challenges at a coarser granularity. DepClean removes unused dependencies, which is, according to our empirical results, a significant cause of program bloat.

Qiu et al. (2016) empirically show evidence that a considerable proportion of API members are not widely used, i.e., many classes, methods, and fields of popular libraries are not used in practice. (Pham et al. 2016) implement a bytecode based analysis tool to learn about the actual API usage of Android frameworks. Hejderup (2015) study the actual usage of modules and dependencies in the Rust ecosystem, and propose PRÄZI, a tool for constructing fine-grained call-based dependency networks (Hejderup et al. 2018). Lämmel et al. (2011) perform a large-scale study on API usage based on the migration of AST code segments. Other studies have focused on understanding how developers use APIs on a daily basis (Roover et al. 2013; Bauer et al. 2014). Some of the motivations include improving API design (Myers and Stylos 2016; Harrand et al. 2019) and increasing developers productivity (Lim 1994). All these studies hint at the presence of bloat in APIs. To sum up, our paper is the first empirical study that explores and consolidates the concept of bloated dependencies in the Maven ecosystem, and is the first to investigate the reaction of developers to bloated dependencies.

Program slicing (Horwitz et al. 1988; Sridharan et al. 2007; Binkley et al. 2019) is a program analysis technique used to compute the subset of statements (“slice”) that affect the values of a given program. Static slicing removes unused code by computing a statement-based dependence graph and identifies the statements that are directly or transitively reachable from a seed on the graph. DepClean uses a similar approach for debloat, where the slices are bytecode calls between dependencies computed by backtracking usages between the artifact and its dependencies.

Dependency Management in Software Ecosystems

Library reuse and dependency management has become mainstream in software development. McIntosh et al. (2012) analyze the evolution of automatic build systems for Java (ANT and Maven). They found that Java build systems follow linear or exponential evolution patterns in terms of size and complexity. In this context, we interpret bloated dependencies as a consequence of the tendency of build automation systems of evolving towards open-ended complexity over time.

Decan et al. (2019, 2017) studied the fragility of packaging ecosystems caused by the increasing number of transitive dependencies. Their findings corroborate our results, showing that most clients have few direct dependencies but a high number of transitive dependencies. They also found that popular libraries tend to have larger dependency trees. However, their work focuses primarily on the relation between the library users and their direct providers and does not take into account the inherited or transitive dependencies of those providers. We are the first, to the best of our knowledge, to conduct an empirical analysis of bloated dependencies in the Maven ecosystem considering both, users and providers, as potential sources of software bloat.

Bezemer et al. (2017) performed a study of unspecified dependencies, i.e., dependencies that are not explicitly declared in the build systems. They found that these unspecified dependencies are subtle and difficult to detect in make-based build systems. Seo et al. (2014) analyzed over 26 millions builds in Google to investigate the causes, types of errors made, and resolution efforts to fix the failing builds. Their results indicate that, independent of the programming language, dependency errors are the most common cause of failures, representing more than two thirds of fails for Java. Based on our results, we hypothesize that removing dependency bloat would reduce spurious CI errors related to dependencies.

Jezek and Dietrich (2014) describe, with practical examples, the issues caused by transitive dependencies in Maven. They propose a static analysis approach for finding missing, redundant, incompatible, and conflicting API members in dependencies. Their experiments, based on a dataset of 29 Maven projects, show that problems related to transitive dependency are common in practice. They identify the use of wrong dependency scopes as a primary cause of redundancy. Our quantitative study extends this work to the scale of the Maven Central ecosystem, and provides additional evidence about the persistence of the dependency redundancy problem.

Callo Arias et al. (2011) performed a systematic review about dependency analysis solutions in software-intensive systems. Bavota et al. (2015) studied performed an empirical study on the evolution of declared dependencies in the Apache community. They found that build system specifications tend to grow over time unless explicit effort is put into refactoring them. Our qualitative results complement previous studies that present empirical evidence that developers do not systematically update their dependency configuration files (McIntosh et al. 2014; Kula et al. 2018).

Conclusion

In this work, we presented a novel conceptual analysis of a phenomenon originated from the practice of software reuse, which we coined as bloated dependencies. This type of dependency relationship between software artifacts is intriguing: from the perspective of the dependency management systems that are unable to avoid it, and from the standpoint of developers who declare dependencies but do not use them in their applications.

We performed a quantitative and qualitative study of bloated dependencies in the Maven ecosystem. To do so, we implemented a tool, DepClean, which analyzes the bytecode of an artifact and all its dependencies that are resolved by Maven. As a result of the analysis, DepClean provides a report of the bloated dependencies, as well as a new version of its POM file which removes the bloat. We use DepClean to analyze the 723,444 dependency relationships of 9,639 artifacts in Maven Central. Our results reveal that 75.1% of them are bloated (2.7% are direct dependencies, 15.4% are inherited from parent POMs, and 57% are transitive dependencies). Based on these results, we distilled two possible causes: the cascade of unwanted transitive dependencies induced by direct dependencies, and the dependency heritage mechanism of multi-module Maven projects.

We complemented our quantitative study of bloated dependencies with an in-depth qualitative analysis of 30 mature Java projects. We used DepClean to analyze these projects and submitted the results obtained as pull request on GitHub. Our results indicated that developers are willing to remove bloated-direct dependencies: 16 out of 17 answered pull requests were accepted and merged by the developers in their code base. On the other hand, we found that developers tend to be skeptical regarding the exclusion of bloated-transitive dependencies: 5 out of 9 answered pull requests were accepted. Overall, the feedback from developers revealed that the removal of bloated dependencies clearly worth the additional analysis and effort.

Our study stresses the need to engineer, i.e., analyze, maintain, test POM files. The feedback from developers shows interest in DepClean to address this challenge. While the tool is robust enough to analyze a variety of real-world projects, developers now ask questions related to the methodology for dependency debloating, e.g., when to analyze bloat? (in every build, in every release, after every POM change), who is responsible for debloat of direct or transitive dependencies? (the lead developers, any external contributor), how to properly managing complex dependency trees to avoid dependency conflicts? These methodological questions are part of the future work to further consolidate DepClean.