Java Omnibus
OOAD & Architecture / Design principles / Dependency Inversion Principle in Java
Design Principles Java Order Management
Design Principles

Dependency Inversion matters because high-level business policy should not be trapped behind low-level technical detail.

DIP is often explained as “depend on abstractions, not concretions.” That is useful, but incomplete. The real intent is architectural: high-level policy should define the shape of the collaboration, while low-level detail should plug into it. In other words, the core business flow should not be authored around vendor APIs, transport concerns, or framework mechanics.

Why This Topic Matters

Why it matters in real Java systems

In order systems, the use case for placing an order should describe inventory reservation, payment authorization, and confirmation notification in business terms. It should not be structured around the internals of one payment gateway or one messaging client. DIP keeps the core decision-making layer in control.

Core Explanation

The design idea behind this topic

DIP becomes most powerful when you think in layers. High-level policies define the purpose of the system. Low-level details implement storage, messaging, integration, and transport. If high-level code depends directly on low-level APIs, the dependency direction is backwards from the design intent.

In Java and Spring systems, dependency injection often makes DIP easier to implement, but DI is not the same thing as DIP. You can inject a concrete detail into a use case and still violate the principle if the business flow is authored around that detail.

The strongest DIP designs let the use case speak the language of the domain while details adapt themselves to that language.

Concept Breakdown

Key ideas to hold onto

Concept

Policy above detail

Business flow should describe intent first, then delegate details through stable contracts.

Concept

Abstractions owned by the high level

The important abstraction is often shaped by the use case, not by the vendor API.

Concept

DIP is architectural

This principle helps packages and modules, not just classes and constructors.

Java Example

Java example in the Order Management domain

Java example: use case depends on its own abstractions
package org.javaomnibus.ecommerce.principles;

public final class ConfirmPaymentUseCase {
    private final PaymentStatusGateway paymentStatusGateway;
    private final OrderRepository orderRepository;

    public ConfirmPaymentUseCase(PaymentStatusGateway paymentStatusGateway, OrderRepository orderRepository) {
        this.paymentStatusGateway = paymentStatusGateway;
        this.orderRepository = orderRepository;
    }

    public void confirm(String orderId) {
        PaymentStatus paymentStatus = paymentStatusGateway.statusFor(orderId);
        if (paymentStatus == PaymentStatus.APPROVED) {
            orderRepository.markPaid(orderId);
        }
    }
}

interface PaymentStatusGateway {
    PaymentStatus statusFor(String orderId);
}

interface OrderRepository {
    void markPaid(String orderId);
}

enum PaymentStatus { APPROVED, DECLINED }
Code Walkthrough

Step by step

  1. ConfirmPaymentUseCase depends on the abstractions it needs to express its policy.
  2. The payment gateway contract is phrased in domain-relevant terms, not vendor client specifics.
  3. The repository abstraction also reflects a business-facing operation rather than low-level persistence mechanics.
  4. Low-level adapters can implement these interfaces without owning the structure of the use case.
Real-World Usage

Where this shows up in practice

  • Spring Boot applications where core use cases should remain independent of infrastructure details
  • Ports-and-adapters or hexagonal designs in modular monoliths
  • Payment, shipment, and notification integrations that should stay replaceable
Trade-Offs

When not to over-apply it

DIP can introduce extra abstractions, and those abstractions should earn their existence. If a dependency is simple, stable, and unlikely to vary, a direct dependency may be acceptable. The principle is about dependency direction where it matters most.

Common Mistakes

Frequent mistakes to watch for

  • Assuming constructor injection automatically means DIP is satisfied
  • Letting vendor-specific terminology leak into high-level abstractions
  • Introducing generic interfaces that do not express business meaning
  • Ignoring package and module dependency direction while only fixing single classes
Related Pages

Related concepts


What To Read Next