Build Tools · Lesson 9
Dependency Scopes and Conflicts
Controlling Dependencies
A dependency has a scope that determines where it is available: compilation, testing, runtime, or packaging. Correct scopes keep production artifacts small and prevent test-only libraries from leaking into deployed applications. Version conflicts occur when different libraries request incompatible versions of the same module.
Declaring a Runtime Boundary
Application code should depend on stable APIs and let the build tool resolve implementation details. This Java example uses an interface so the runtime implementation can be selected by configuration.
public interface NotificationSender {
void send(String recipient, String message);
}
public class CourseNotificationService {
private final NotificationSender sender;
public CourseNotificationService(NotificationSender sender) {
this.sender = sender;
}
public void announceLesson(String recipient, String lessonTitle) {
sender.send(recipient, "New lesson available: " + lessonTitle);
}
}
Scope Comparison
| Scope | Compile code | Test code | Runtime artifact |
|---|---|---|---|
| Main/compile | Yes | Yes | Usually yes |
| Runtime | No | Yes | Yes |
| Test | No | Yes | No |
| Provided/compile-only | Yes | Yes | Container supplies it |
Common Mistake and Practices
Common Mistake: Fixing a version conflict by randomly forcing the newest transitive dependency. The build may pass while an older library expects incompatible behavior at runtime.
- Inspect the dependency tree before forcing versions.
- Prefer compatible library releases.
- Keep test tools test-scoped.
- Use dependency locking for repeatability.
- Review upgrades with integration tests.
Real-World Use Case
A web application marks its test framework as test-only and relies on the servlet container for provided APIs. The production artifact contains only the libraries it actually needs to run.
Quiz Check