Recently, my good friend Richard Fichtner advised using the mvn dependency:analyze command to get rid of declared but unused dependencies: mvn dependency:analyze https://bsky.app/profile/did:plc:cc2k5egfzqpf3nbjrs5xox4r/post/3lcxfnsc2h62m?ref_src=embed&embedable=true https://bsky.app/profile/did:plc:cc2k5egfzqpf3nbjrs5xox4r/post/3lcxfnsc2h62m?ref_src=embed&embedable=true While it was a great idea years ago, it's dangerous advice today. In this post, I'd like to explain what the plugin does and why you shouldn't use it but in the most straightforward projects. The mvn dependency:analyze command mvn dependency:analyze Maven uses a plugin architecture; in the above command, the plugin is maven-dependency-plugin. A plugin hosts several related goals. Here, it's analyze. maven-dependency-plugin goals analyze Analyzes the dependencies of this project and determines which are: used and declared; used and undeclared; unused and declared. This goal is intended to be used standalone, thus it always executes the test-compile phase - use the dependency:analyze-only goal instead when participating in the build lifecycle. By default, maven-dependency-analyzer is used to perform the analysis, with limitations due to the fact that it works at bytecode level, but any analyzer can be plugged in through analyzer parameter.-- dependency:analyze Analyzes the dependencies of this project and determines which are: used and declared; used and undeclared; unused and declared. This goal is intended to be used standalone, thus it always executes the test-compile phase - use the dependency:analyze-only goal instead when participating in the build lifecycle. test-compile dependency:analyze-only By default, maven-dependency-analyzer is used to perform the analysis, with limitations due to the fact that it works at bytecode level, but any analyzer can be plugged in through analyzer parameter. maven-dependency-analyzer analyzer -- dependency:analyze dependency:analyze maven-dependency-analyzer is a shared Maven component. Its description is quite descriptive: maven-dependency-analyzer Analyzes the dependencies of a project for undeclared or unused artifacts. Warning: Because analysis is done on the bytecode rather than the source, some cases are not detected including constants, annotations with source-only retention, and links in Javadoc. This can lead to incorrect results when these are the only uses of a dependency. The main component is ProjectDependencyAnalyzer, which uses ClassAnalyzer and DependencyAnalyzer.-- maven-dependency-analyzer Analyzes the dependencies of a project for undeclared or unused artifacts. Warning: Because analysis is done on the bytecode rather than the source, some cases are not detected including constants, annotations with source-only retention, and links in Javadoc. This can lead to incorrect results when these are the only uses of a dependency. Warning bytecode The main component is ProjectDependencyAnalyzer, which uses ClassAnalyzer and DependencyAnalyzer. ProjectDependencyAnalyzer ClassAnalyzer DependencyAnalyzer -- maven-dependency-analyzer maven-dependency-analyzer The warning clearly shows that it works at the bytecode level. In particular, it explicitly mentions that it doesn't consider source-level annotations. bytecode Spring Boot starters I described how to design your own Spring Boot starter a long time ago, and it didn't change a lot since then. If you're new to Spring Boot starters, here's a summary. design your own Spring Boot starter SpringBoot relies on AutoConfiguration classes. AutoConfiguration classes are regular configuration classes, i.e., they contribute to the application classes. You can set specific activation criteria, such as the presence of a Spring property, but these are not specific to auto-configuration. i.e. Here's a very simplified flow: The JAR that automatically comes with Spring Boot is org.springframework.boot:spring-boot-autoconfigure. You can check the content of its META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: org.springframework.boot:spring-boot-autoconfigure META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ... org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.ReactiveMultipartAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration ... org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.ReactiveMultipartAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration As an example, here's the RestClientAutoConfiguration: RestClientAutoConfiguration @AutoConfiguration(after = { HttpClientAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class }) //1 @ConditionalOnClass(RestTemplate.class) //2 @Conditional(NotReactiveWebApplicationCondition.class) //3 public class RestTemplateAutoConfiguration { // Class body } @AutoConfiguration(after = { HttpClientAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class }) //1 @ConditionalOnClass(RestTemplate.class) //2 @Conditional(NotReactiveWebApplicationCondition.class) //3 public class RestTemplateAutoConfiguration { // Class body } Set the order of auto-configuration classes Activate if the RestTemplate class is on the classpath Activate if we aren't in a reactive web app context Set the order of auto-configuration classes Activate if the RestTemplate class is on the classpath RestTemplate Activate if we aren't in a reactive web app context Note that the class loader loads the RestTemplateAutoConfiguration class just fine, regardless of whether the RestTemplate class is on the classpath or not! Spring leverages this mechanism to its fullest, as seen above. In effect, the resolution of classes configured in annotations is deferred until they are explicitly accessed. RestTemplateAutoConfiguration regardless of whether the RestTemplate explicitly Bringing the maven-dependency-analyzer into the modern age maven-dependency-analyzer Committers designed the analyzer in 2007: here's how it looked like then. Spring Boot started later, in 2010. For this reason, the analyzer didn't take deferred class loading in annotations. Note that this is still not the case; the project doesn't get a lot of love. here's When using the plugin on a Spring Boot project, you'll get a lot of false positives. I tried it with a simple Spring Boot project, using WebFlux and R2DBC on PostgreSQL. Here's a slight excerpt of the output when I run mvn analyze:dependencies: mvn analyze:dependencies [WARNING] Unused declared dependencies found: [WARNING] org.springframework.boot:spring-boot-starter-data-r2dbc:jar:3.4.0:compile [WARNING] org.testcontainers:postgresql:jar:1.20.4:test [WARNING] org.testcontainers:r2dbc:jar:1.20.4:test [WARNING] Unused declared dependencies found: [WARNING] org.springframework.boot:spring-boot-starter-data-r2dbc:jar:3.4.0:compile [WARNING] org.testcontainers:postgresql:jar:1.20.4:test [WARNING] org.testcontainers:r2dbc:jar:1.20.4:test If I remove any of these dependencies, tests don't run. What would be necessary to make the analyzer work with Spring Boot projects? Let's analyze the analyzer. The plugin allows configuring another analyzer: Specify the project dependency analyzer to use (plexus component role-hint). By default, maven-dependency-analyzer is used. To use this, you must declare a dependency for this plugin that contains the code for the analyzer. The analyzer must have a declared Plexus role name, and you specify the role name here. Type: java.lang.String Since: 2.2 Required: No User Property: analyzer Default: default -- dependency:analyze Specify the project dependency analyzer to use (plexus component role-hint). By default, maven-dependency-analyzer is used. To use this, you must declare a dependency for this plugin that contains the code for the analyzer. The analyzer must have a declared Plexus role name, and you specify the role name here. Type: java.lang.String Since: 2.2 Required: No User Property: analyzer Default: default Type: java.lang.String Type: java.lang.String Type java.lang.String Since: 2.2 Since: 2.2 Since 2.2 Required: No Required: No Required No User Property: analyzer User Property: analyzer User Property analyzer Default: default Default: default Default default -- dependency:analyze dependency:analyze We can create an overall analyzer that reuses the above but adds one specific to Spring Boot. Conclusion The current state of the Maven analyzer doesn't offer any benefit to modern Spring Boot projects. The existing code is open to configuration and even extension. However, we would need to embed a lot of Spring Boot logic. For Quarkus and Micronaut projects, we would require dedicated code as well. I don't know if it's worth the time and effort. If you think it is, I hope this blog post can serve as an early-stage analysis. To go further: To go further: dependency:analyze Maven Dependency Analyzer Designing your own Spring Boot starter – part 1 Designing your own Spring Boot starter – part 2 dependency:analyze dependency:analyze Maven Dependency Analyzer Maven Dependency Analyzer Designing your own Spring Boot starter – part 1 Designing your own Spring Boot starter – part 1 Designing your own Spring Boot starter – part 2 Designing your own Spring Boot starter – part 2 Originally published at A Java Geek on March 9th, 2025 Originally published at A Java Geek on March 9th, 2025 A Java Geek