When new can conflict with DIP
Fast recall
Quick revision
Core answer
new is not automatically a DIP violation. Every object must be constructed somewhere. It becomes a design problem when high-level policy creates a changeable low-level service inside itself and therefore owns both selection and use. Construct value objects and private implementation details normally; assemble replaceable external collaborators in a composition root or factory.Interview-ready guide
Interview answer
Using new inside a class can conflict with DIP when the class is high-level policy and the object is a changeable low-level dependency. If OrderService creates new StripeClient(...), it chooses the provider, owns configuration, and cannot be assembled with another gateway without editing the service.
But new itself is not wrong. The application must construct concrete objects at a composition root, and domain code can create values such as Money or internal collections. I move construction of replaceable external collaborators to configuration or a factory, then pass the required dependency into the policy. The design question is who should choose the implementation and how volatile it is—not whether the source contains the word new.
When new can conflict with DIP
Direct Answer
new is not automatically a DIP violation. Every object must be constructed somewhere. It becomes a design problem when high-level policy creates a changeable low-level service inside itself and therefore owns both selection and use. Construct value objects and private implementation details normally; assemble replaceable external collaborators in a composition root or factory.
Code Example
// High-level policy selects a vendor detail itself.
final class OrderService {
void process(long orderId) {
OrderRepository repository = new MySqlOrderRepository();
Order order = repository.findById(orderId);
repository.save(order);
}
}
// Policy receives the capability; bootstrap code selects the adapter.
final class OrderService {
private final OrderRepository repository;
OrderService(OrderRepository repository) {
this.repository = repository;
}
void process(long orderId) {
Order order = repository.findById(orderId);
repository.save(order);
Money fee = new Money(100); // ordinary value construction
}
}
OrderService service = new OrderService(new MySqlOrderRepository());
The first design is problematic when the repository is a volatile boundary the policy should not choose. The last line still uses new, but it does so at the assembly boundary where concrete implementations must be selected.
Common Mistakes & Pitfalls
- Banning
newfrom business code: value construction is normal. - Moving every object to a DI container: containers are not needed for local values and simple private helpers.
- Injecting a concrete vendor and calling it DIP: construction moved, but source dependency may still point outward.
- Creating interfaces without a useful contract: indirection alone is not inversion.
- Ignoring the composition root: concrete implementations must be chosen somewhere.
Check your understanding
Answer these without looking back at the article.
- What is tight coupling and how does it hurt testability?
- Are there cases where using new is acceptable?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.