Last Updated: February 12, 2026
Have you ever called a method on an object… then chained another… and another… until the line looked like a trail of dots?
Or made a small internal change to one class, and suddenly had to update code across five other layers?
If yes, you’ve probably run into a violation of one of the most overlooked design principles in software engineering: The Law of Demeter (LoD).
Let’s understand it with a real-world example and see why this principle matters more than you might think.
Imagine you're building a simple e-commerce system.
You have:
Customer who owns a ShoppingCartCartItemsCartItem refers to a ProductProduct has a PriceNow let’s say you want to display the price of the first product in a customer’s shopping cart.
A common (but flawed) approach would be to write something like this:
Look at that chain. OrderService is coupled to six classes deep. It knows about the customer's cart, the cart's internal list, the structure of a cart item, the product inside it, and the price object. A change to any of these classes could break the OrderService, even though those classes have nothing to do with order processing.
This is called a "train wreck" or "dot-chaining": one object reaching through several others to get what it wants. You start with a Customer, go into their ShoppingCart, peek into its internal list of CartItems, grab the first one, extract the Product, and finally get the Price.
Here is the full function that uses this pattern:
This approach works. It compiles. It runs. But it smells bad, and it will cause real pain as your system evolves.
The OrderService method is now tightly coupled to the entire internal structure of the customer and their cart.
ShoppingCart changes how it stores items (e.g., using a Map instead of a List)CartItem renames its getProduct() methodProduct evolves to store pricing in a new way…Boom. Your OrderService code breaks. Even though it had nothing to do with those decisions.
You are reaching deep into object internals, violating encapsulation at multiple levels.
Customer exposes its ShoppingCartShoppingCart exposes its internal listCartItem and Product just to get a priceEvery layer of reach is a layer of exposed internals. The whole point of encapsulation is to hide these details, and this code tears down every wall.
Imagine this change: You switch from using a Money wrapper to a BigDecimal for price representation in Product.
Now, every part of your codebase that dot-chased its way to product.getPrice() must be updated.
Testing displayFirstItemPrice() becomes a mocking marathon.
To test it in isolation, you'd need to mock:
CustomerShoppingCartListCartItemProductPriceOne function. Six mocks. That is exhausting, fragile, and a clear sign something is wrong with the design.
“Only talk to your immediate friends.” — Law of Demeter
The Law of Demeter, also known as the Principle of Least Knowledge, was formulated in 1987 at Northeastern University during work on the Demeter project. Despite its age, it remains one of the most practical guidelines for writing maintainable object-oriented code.
The rule is straightforward. A method M on an object O should only call methods on:
O)O holds as instance variables)M)M)That's it. In plain terms: don't reach through one object to get to another.
If you call a.getB().getC().doSomething(), you are violating LoD because you are reaching through B to talk to C. You should only be talking to A, and let A figure out how to get the job done.
Let's rewrite the e-commerce example in a cleaner, more respectful way. The strategy is to push the responsibility down to the classes that own the data. Each class will expose a meaningful method instead of exposing its internals.
ShoppingCartThe ShoppingCart knows about its items. So it should be the one to answer questions about them.
Notice that ShoppingCart still reaches into CartItem and Product. That is fine here because ShoppingCart owns the items. The chain stays within the cart's own responsibility boundary. The important thing is that external callers no longer need to know about these internals.
CustomerThe Customer owns the ShoppingCart, so it should be the one to delegate cart-related queries.
OrderServiceNow the OrderService only talks to its direct friend: the Customer.
Much better. Now the dependency chain looks completely different:
OrderService only talks to Customer. Customer only talks to ShoppingCart. Each layer hides the next one. Now OrderService does not care about:
CartItem containsProduct holds its priceIt just asks the Customer for what it needs, and the Customer delegates to the objects it owns. This is the Law of Demeter at work.
Each class depends only on its immediate collaborators. Code changes in one place don't ripple across your codebase. When ShoppingCart changes its internal storage from a List to a Map, only ShoppingCart needs updating.
Each class handles its own logic. No external code peeks into internals. Objects expose meaningful behaviors ("give me the first item price") instead of raw structure ("give me your list so I can dig through it").
You can evolve internal implementations without affecting consumers. If Product changes how it stores pricing, only CartItem and ShoppingCart need to adapt. The OrderService remains untouched.
Fewer mocks are needed. To test the refactored displayFirstItemPrice, you only need to mock Customer and have it return a Money value. No more six-layer mock chains.
Public methods become expressive, intentional, and meaningful. Instead of forcing callers to navigate your object graph, you provide clear entry points that describe what the caller actually wants.
Yes, following LoD can result in additional small, delegating methods. But this “extra” code serves a critical purpose: it reduces coupling.
Think of it this way: would you rather write 3 lines now to isolate behavior, or refactor 300 later when your system breaks?
These small wrappers protect the rest of your codebase from internal changes. They enforce the principle of “Tell, Don’t Ask”, you tell an object what you want it to do, instead of reaching inside and doing it yourself.
Not at all. LoD doesn’t forbid getters.
Simple property access like customer.getName() is perfectly fine—name is a direct part of Customer.
The issue arises with chained getters across object boundaries:
This creates tight coupling between the caller and the internal structure of several unrelated classes. Instead, you’re encouraged to delegate the operation to the object that owns the knowledge.
size() on a list I get from an object?This is a nuanced area.
If getUsers() returns a standard collection like List<User>, then getUsers().size() is generally acceptable. Lists are transparent and well-understood abstractions, and operations like size() don’t break encapsulation.
However, this would be a violation:
The more layers of domain objects you traverse, the more you're violating LoD.The key is whether you're interacting with a simple data structure or delving into another object’s responsibility chain.
Like most principles, LoD is a guideline—not a hard rule. Some common exceptions include:
Map.get() or List.size()) is typically safe.The key is intentional design. If you understand the coupling trade-off and still find it justifiable, go for it. Just don’t do it by accident.
In short: LoD is like a guardrail. It helps you avoid the slippery slope of exposing internals and tying your code together too tightly.
Let's look at another scenario to reinforce the pattern. This time, we are building a notification system for a ride-sharing app like Uber or Lyft.
A NotificationService class needs three pieces of information to send a ride update to a passenger:
A developer in a hurry writes the following:
Look at what the NotificationService now knows about. It is coupled to Driver, Profile, Vehicle, Registration, Passenger, ContactInfo, and their internal structures.
If Vehicle restructures how it stores registration data, the notification service breaks. If Passenger changes from ContactInfo to a different contact model, the notification service breaks. Every internal change in any of these classes becomes a potential breaking change for NotificationService.
Instead of letting NotificationService navigate the entire object graph, we add delegation methods to Ride. The Ride class already has references to its driver and passenger, so it is the natural place to answer these questions.
Now the NotificationService becomes simple and clean:
The dependency picture is now dramatically simpler:
NotificationService only knows about Ride. It has no idea that Driver, Profile, Vehicle, Registration, Passenger, or ContactInfo even exist.
Vehicle restructures how it stores registration data, you update Ride.getVehiclePlate() and nothing else.NotificationService, you only mock Ride with three simple return values.getDriverName() and getPassengerPhone() clearly describe what data is being retrieved without exposing how it is obtained.Driver, Vehicle, or Passenger stays contained within the classes that own them.