Skip to main content

Dependency

Dependency is the loosest of the five relationships: no held reference, no field, nothing that outlives a single method call. A ReportGenerator takes a DateFormatter as a parameter, uses it, and forgets it existed the moment the method returns. That's the whole relationship - and because it's so cheap, it's also the one people most often either miss entirely or mistake for something heavier.

Definition: a borrow, not a hold

Two classes have a dependency when one of them uses the other without storing a reference to it anywhere that survives the call: as a method parameter, a local variable created and discarded inside a method, or a return type. The test that actually matters: if DateFormatter's public interface changed, would ReportGenerator need a code change to keep compiling? If yes, there's a dependency, even though ReportGenerator never declares a DateFormatter field.

class ReportGenerator {
String buildHeader(DateFormatter formatter) { // parameter, not a field
return "Report generated: " + formatter.format(LocalDate.now());
}
}

After buildHeader returns, ReportGenerator retains nothing about the DateFormatter it was handed - the next call might receive an entirely different implementation, and ReportGenerator neither notices nor cares.

UML notation

Dependency draws as a dashed line with an open arrowhead, pointing from the dependent class to the one it depends on. No diamond - dependency never implies ownership or a whole/part relationship, just a usage that shows up somewhere in the method signature or body.

ReportGeneratorDateFormatter
ReportGenerator depends on DateFormatter - dashed line, no diamond, no held field.

Compare the dashes here to association's solid line: solid means "I keep a reference," dashed means "I glance at you and move on." Same open arrowhead shape, entirely different commitment.

Multiplicity - and why it usually doesn't apply

Association, aggregation, and composition all get multiplicity annotations because they describe how many objects are connected at once - a standing structural fact about the design. Dependency describes a single method call, not a standing relationship, so "how many DateFormatters does a ReportGenerator depend on" isn't a meaningful question the way "how many Orders does a Customer have" is. You'll rarely see multiplicity written on a dependency arrow for exactly this reason - and if you find yourself wanting to write one, that's often a sign the relationship is actually a held reference (association) rather than a transient one.

Directionality

Dependency is essentially always unidirectional: ReportGenerator depends on DateFormatter, and DateFormatter has no idea ReportGenerator exists, let alone depends on it back. A "mutual dependency" - two classes each taking the other as a parameter somewhere - isn't drawn with two arrowheads the way bidirectional association is; it's usually just two separate, unrelated dependency arrows, and having a lot of them between the same two classes is a sign worth pausing on (see common mistakes).

Worked example

interface DateFormatter {
String format(LocalDate date);
}
 
class IsoDateFormatter implements DateFormatter {
public String format(LocalDate date) {
return date.toString(); // e.g. "2026-08-03"
}
}
 
class ReportGenerator {
String buildHeader(DateFormatter formatter) {
return "Report generated: " + formatter.format(LocalDate.now());
}
 
void export(DateFormatter formatter, List<String> rows) {
String header = buildHeader(formatter); // formatter is reused within the call,
System.out.println(header); // but never assigned to a field
rows.forEach(System.out::println);
}
}

Swap IsoDateFormatter for a UsDateFormatter at the call site and ReportGenerator never notices - which is exactly the point of keeping the relationship this loose. No field means no lifecycle to manage, no synchronization to worry about, and no reference to leak.

Telling dependency apart from association

The test is entirely mechanical: is the reference held as a field?

  • Held as a field, for the object's lifetime -> association.
  • Received as a parameter, created as a local variable, or returned from a method, and never stored -> dependency.

A Driver with a private Car car; field has an association with Car. A Driver.refuel(GasStation station) method that uses station only inside that one method body has a dependency on GasStation - even though both relationships involve one class "using" another, only one of them survives past the method call that triggered it.

Common mistakes

  • Drawing a dependency arrow for every single method call in a codebase. Taken literally, almost every class depends on String, List, and half the standard library - a diagram that includes all of that stops being readable. Reserve dependency arrows for relationships between the domain classes actually being modeled, not for incidental use of common utility types.
  • Mislabeling a held field as a dependency because "it's just used once." If a constructor stores the argument into a field, it's an association from that point on, regardless of how sparingly the field gets read afterward. Storage, not usage frequency, is the test.
  • Treating dependency as if it were free of design cost. A dependency is loose, not weightless - if ReportGenerator calls five different methods across DateFormatter's interface, that's still a real coupling that breaks if DateFormatter changes its contract, even with no field involved.

Try it yourself: a PriceCalculator.applyDiscount(Coupon coupon, double price) method takes a Coupon, reads its discount percentage, and returns an adjusted price - PriceCalculator has no Coupon field anywhere. Is this a dependency or an association? What single code change would flip it into the other one?

Check yourself

Question 1 of 3

A ReportGenerator.buildHeader(DateFormatter formatter) method uses the parameter only inside that method body and stores nothing in a field. What relationship is this, and how is it drawn?