A small change to one class should not routinely force edits in code that is several objects away. Yet this happens when callers navigate through collaborators to reach deeper objects:

order.customer().address().country().code()

The line is compact, but the caller now knows that an order exposes a customer, a customer exposes an address, an address exposes a country, and a country exposes a code. If that structure changes, the caller may need to change even when the business question it asks stays the same.

The Law of Demeter is a design guideline for noticing this kind of coupling. Its practical message is: let code collaborate with objects it directly knows instead of reaching through them to manipulate distant objects.

This article develops that mental model, shows how to improve a message chain without blindly hiding every dot, and explains when direct navigation is the simpler design.

Think in terms of knowledge, not punctuation

The Law of Demeter is sometimes summarized as “only talk to your immediate friends.” That phrase is useful if we make the engineering meaning precise.

Consider a pricing operation that needs to know whether an order is domestic:

if order.customer().address().country().code() == "NL":
    applyDomesticRate()

The problem is not that the expression contains several method calls. The problem is knowledge. Pricing code knows the internal route from an order to the representation of a customer’s country.

Now imagine the address model changes. Perhaps an order starts storing a delivery destination separately from the customer’s billing address. The pricing rule may still be “use the domestic rate for deliveries in the Netherlands,” but the pricing code must change because it knew how to navigate the old model.

A useful question is:

Does this code need this information, or does it merely need an answer from the object that owns the information?

If the caller only needs an answer, moving the navigation behind a meaningful operation can reduce coupling.

Start with the smallest useful change

Suppose the pricing code only needs to decide whether an order is domestic. Instead of exposing the navigation path, the order can expose that decision in domain terms:

if order.isDomestic():
    applyDomesticRate()

The implementation might still navigate internally:

Order.isDomestic():
    return deliveryDestination.countryCode() == "NL"

Nothing magical happened to the object graph. The important change is who knows its shape.

Before the change, pricing code knew how an order represented destination information. After the change, the order owns that knowledge and pricing code depends on a stable question: “Is this order domestic?”

If the representation later changes, isDomestic() may change while the pricing rule remains untouched.

This is the main benefit of the guideline: it can localize the effect of structural changes.

A message chain is a signal, not a verdict

Long navigation chains deserve attention because each step can expose another structural assumption. They are not automatically design errors.

Compare these two expressions:

invoice.customer().billingProfile().currency()

and:

point.translate(10, 5).round().x()

Both contain multiple calls, but they communicate different relationships.

In the first expression, the caller walks through several distinct objects owned by the domain model. It may be depending on their arrangement.

In the second, each operation can return a new value that the caller intentionally transforms. There may be no hidden object graph to protect. Fluent APIs, builders, query objects, and immutable value transformations commonly use chaining without creating the same kind of structural coupling.

Counting dots is therefore a poor rule. Ask what each call reveals about ownership and structure.

Put behavior where the required knowledge belongs

A common response to a message chain is to add forwarding methods mechanically:

order.customerCountryCode()

That can shorten the chain without improving the design. Pricing code still asks for a low-level country code and interprets it itself.

A stronger boundary often expresses the caller’s intent:

order.isDomestic()

The difference matters. customerCountryCode() hides navigation but still exports representation knowledge. isDomestic() can hide both navigation and the rule for answering the business question.

This does not mean every decision belongs on Order. Ownership depends on the model. If domestic pricing varies by merchant, shipping service, and destination, a separate policy may be a better home:

pricingPolicy.isDomestic(order.destination())

Here the policy directly receives the value it needs. The goal is not to push every operation into one object. The goal is to avoid making unrelated callers understand internal paths through other objects.

Distinguish collaborators from strangers

A practical version of the guideline is to let a method work mainly with objects that are already part of its immediate context. Depending on the programming style, these often include:

  • the current object;
  • arguments passed to the method;
  • objects the current object directly owns or is configured with;
  • objects the method creates for its own work.

The risky step is often obtaining an object from one collaborator and then using that returned object to reach still deeper into the model.

For example:

shipment.route().carrier().contract().maximumWeight()

Shipping code is no longer collaborating only with shipment. It understands several relationships behind it.

If the real question is whether the shipment exceeds its contractual limit, a boundary such as this may communicate the intent better:

shipment.exceedsWeightLimit()

Or, if contract rules belong to a shipping policy:

shippingPolicy.accepts(shipment)

Which design is preferable depends on who should own the rule. The Law of Demeter identifies the coupling pressure; it does not decide the domain model for you.

Why deep navigation makes change spread

Deep navigation creates a dependency on a path, not merely on the final value.

Suppose code uses:

account.subscription().plan().limits().maxProjects()

The caller may be affected by several independent changes:

  1. Account stops exposing Subscription directly.
  2. Plan information moves behind an entitlement service.
  3. Limits become calculated instead of stored.
  4. maxProjects becomes a policy decision based on account state.

The caller only wanted to know whether another project can be created. Knowing the path gives it more reasons to change than its responsibility requires.

An operation such as:

account.canCreateProject()

can reduce those reasons if account entitlement is genuinely the account’s responsibility. The structural changes can then remain behind that operation.

This is why the guideline is closely related to maintainability: reducing unnecessary knowledge reduces the number of places that must coordinate when representations evolve.

Do not replace navigation with forwarding layers

The most common misuse is creating a chain of pass-through methods solely to satisfy the rule:

Order.countryCode()
    -> Customer.countryCode()
        -> Address.countryCode()
            -> Country.code()

The caller sees one method, but the system still contains a brittle chain. Worse, several classes now expose methods they do not conceptually own.

Forwarding can be appropriate when the outer object deliberately provides a stable abstraction over an inner object. It is weak when the forwarding method exists only to conceal syntax.

Prefer asking what capability belongs at the boundary. If no meaningful capability exists, direct access may be more honest.

Avoid turning objects into oversized service surfaces

Another failure mode is adding every possible question to a top-level object:

order.customerEmail()
order.customerCountry()
order.customerTaxRegion()
order.customerPreferredLanguage()
order.customerCreditStatus()

This can make Order a directory for unrelated information rather than a coherent abstraction. The object becomes coupled to all the concepts it forwards.

When callers legitimately need several pieces of customer information, giving them a Customer or a purpose-built value may be simpler. When they need a decision, a domain operation or policy may be better.

The useful trade-off is between two forms of coupling:

  • exposing too much internal structure to callers;
  • making outer objects depend on and forward every inner capability.

The Law of Demeter helps reveal the first problem. Good module and domain design must also avoid the second.

Direct navigation can be the right choice

A simpler approach is preferable when the structure is intentionally public and stable enough for the context.

Data-transfer objects are a common example. If an API response is explicitly a tree of data and application code is meant to inspect that tree, navigating response.user.address.country may simply be using the contract as designed.

The same can be true for configuration objects, syntax trees, document models, or small immutable records. Wrapping every access behind behavior can add indirection without protecting a meaningful decision.

Direct navigation is also reasonable in short-lived transformation code where the job is specifically to map one known structure into another. In that case, knowledge of the source structure is part of the component’s responsibility.

Use the guideline when a caller is learning details outside its responsibility, not merely whenever one object returns another.

Use the rule during code review

When a message chain appears in a change, review it with a few concrete questions:

  1. What question is the caller actually trying to answer?
  2. Which object or policy should own the knowledge needed to answer it?
  3. Is the caller depending on an internal path that may change independently of its own responsibility?
  4. Would a meaningful operation hide that knowledge, or would it merely create forwarding methods?
  5. Is the traversed structure intentionally public data, in which case direct navigation is clearer?

These questions turn the Law of Demeter from a style rule into a design diagnostic.

The goal is not the shortest expression. It is a dependency structure in which each piece of code knows only the details it needs to do its job.

Conclusion

The Law of Demeter is most useful as a warning about knowledge that crosses boundaries. A chain such as a.b().c().d() deserves attention when it makes a caller understand the internal relationships of another part of the system.

Improve that design by identifying the caller’s real intent and placing the necessary knowledge behind a boundary that genuinely owns it. Do not count dots, create forwarding methods mechanically, or hide structures that are intentionally public.

When applied this way, the guideline gives you a practical test for coupling: if a structural change deep inside one object repeatedly forces unrelated callers to change, those callers probably know more than they need to.