When you take an auto from Ameerpet to Kukatpally, you use the auto for twenty minutes, pay the driver and get down. You don't own the auto, you don't keep its number in your contacts, and tomorrow you might take a completely different one. You still needed it to get the job done. In object-oriented design this short, "I only need you for this one task" link between two classes is called a dependency.
A dependency exists when one class uses another class to do its work, but does not keep a lasting reference to it. It is the weakest relationship between classes, often described as "uses-a".
Real-world analogy: a tailor uses a measuring tape while stitching your shirt. The tape is picked up, used and put back. If the shop changes to a different brand of tape tomorrow, the tailor's work hardly changes. But if the tape's markings change from inches to centimetres, the tailor has to adjust. That is the essence of a dependency: a brief use, but a real effect when the other side changes.
1. What Is a Dependency?
Class A depends on class B when A needs B to compile or to run, but A does not store B as one of its fields. The link lives only inside a method. Typical signs:
- B is the type of a method parameter in A.
- A creates a B as a local variable inside a method and throws it away afterwards.
- A method of A returns a B.
- A calls a static method of B.
In every case the relationship ends when the method returns. That short life is what separates a dependency from an association, where A keeps B in a field and knows about it all the time.
2. How Dependency Looks in UML
A dependency is drawn as a dashed arrow pointing from the class that uses to the class that is used. It is the lightest line in UML, which suits the lightest relationship.
Read it as "TollBooth uses Vehicle". Notice what is not in the TollBooth box: there is no Vehicle field.
3. A First Example: A Toll Booth
A toll booth on the Outer Ring Road needs each vehicle for only a moment. It looks at the vehicle type, charges the fee and lets it go. It keeps a running total of money collected, but it never remembers the vehicles themselves.
Output
ORR Toll Plaza: TS09 AB 1234 (car) paid Rs 95
ORR Toll Plaza: TS08 Z 0071 (bus) paid Rs 320
ORR Toll Plaza: AP16 TC 5520 (truck) paid Rs 470
Total collected: Rs 885
Three things make this a dependency. Vehicle appears only as the parameter of charge(). TollBooth has no field of type Vehicle, so after charge() returns, the booth has no idea which vehicles passed. And the vehicles are created outside the booth, so it has nothing to do with their life.
If you added a field like private Vehicle lastVehicle; and kept it, the relationship would become an association, because the booth would now remember a vehicle beyond a single call.
4. Four Ways Dependencies Show Up in Java
Parameters are the most common form, but they are not the only one. The electricity bill generator below depends on four different classes, each in a different way, and none of them is stored in a field.
Output
Service 1102-4471 | 260 units | Rs 1176.60
Service 1102-4471 | 85 units | Rs 270.30
| Form | In the code | Why it is still a dependency |
|---|---|---|
| Parameter | generate(MeterReading reading) | The reading comes in, is used and is gone after the call |
| Local variable | SlabCalculator slabs = new SlabCalculator(); | Created inside the method and never escapes it |
| Static call | Duty.add(energy) | No object is kept at all, only the class is used |
| Return type | return new Bill(...) | The generator makes bills but does not hold on to them |
The import list at the top of a Java file is a quick way to spot dependencies too. Every type you import and use is something your class depends on, even if it never appears as a field.
5. Why Dependencies Matter
A dependency is weak, but it is not free. If MeterReading renames units(), the bill generator stops compiling. If Duty changes the tax rate, every bill changes. The more classes you depend on, and the more concrete and unstable they are, the more often your class has to change for reasons that have nothing to do with its own job.
Two habits keep this under control: depend on as few classes as you really need, and where you can, depend on an interface rather than a concrete class. The next example shows why the second habit matters so much.
6. Hard-Wired Dependencies vs Injected Dependencies
Suppose the app needs to send an OTP. The quickest way to write it is to let the OTP service create its own SMS gateway. It works, but the decision "OTPs go by SMS" is now baked into the class forever. Want WhatsApp for some users? You have to edit the service. Want to test the service without sending a real SMS? You can't.
The better way is to make the service depend on a small interface, MessageChannel, and let whoever creates the service decide which channel to use. This is called dependency injection: the class receives what it needs instead of creating it.
Output
[SMS to 98480 12345] Your OTP is 1111
[SMS to 98480 12345] Your ExamAdda OTP is 4821
[WhatsApp to 98480 12345] Your ExamAdda OTP is 7390
Test check: 1 message recorded, test-user -> Your ExamAdda OTP is 0000
Look at what the injected version buys you. The same OtpService sends by SMS in one place and by WhatsApp in another, and a test can pass a FakeChannel that just records the message. None of this needed a single change inside OtpService.
One honest detail: because OtpService keeps the channel in a field, strictly speaking its link to the channel is an association. The point of the example is not the field. It is that the service now depends on an abstraction instead of a concrete class, and that it does not create its collaborator itself. In Spring Boot you get the same thing with constructor injection, where the framework creates the objects and passes them in for you.
7. A Practical Example: Publishing Exam Results
When results are published, the system needs to score an answer sheet, work out a grade and notify the student. Each of those jobs belongs to a separate class. The publisher only coordinates them, and it receives every helper as a method parameter, so it keeps no fields at all.
Output
Sita: 8/10 = 80% -> Grade A
[Notify Sita] Your result: 80%, Grade A
Kiran: 5/10 = 50% -> Grade C
[Notify Kiran] Your result: 50%, Grade C
Test: 10/10 = 100% -> Grade A
Recorded for test: Test: Your result: 100%, Grade A
Because everything comes in through publish(), testing is easy. The last call passes a RecordingNotifier, which stores the message instead of printing it, so a test can check exactly what the student would have received. Swapping the grading rules for another exam would just mean passing a different GradeTable.
8. Dependency Compared With the Other Relationships
Class relationships form a ladder, from a brief use to full ownership:
| Dependency | Association | |
|---|---|---|
| Stored as a field | No | Yes |
| How long the link lasts | One method call | As long as the object lives |
| UML line | Dashed arrow | Solid line |
| Example | TollBooth uses a Vehicle in charge() | A Driver is assigned a Cab and keeps it |
9. Common Mistakes
- Storing something in a field "just in case" when it is only needed inside one method. That turns a light dependency into a permanent association.
- Creating concrete collaborators with
newdeep inside business logic, which makes the class impossible to test with fakes. - Passing a huge object when the method needs one value from it. If
charge()only needs the vehicle type, a smaller parameter keeps the dependency smaller. - Methods with eight or ten collaborator parameters. That usually means the class is doing too much and should be split.
- Calling static utility methods everywhere for things that may change, such as tax rates or current time. These hidden dependencies are hard to replace in tests.
Summary
- A dependency is a short "uses-a" relationship: A needs B for a task but does not keep it.
- In UML it is a dashed arrow from the user to the used class.
- In Java it appears as a parameter, a local variable, a return type or a static call.
- Keep dependencies few and point them at interfaces. Let callers inject collaborators instead of creating them inside the class.
- Code written this way is easier to change, easier to test and easier to reuse.