Spring boot - Service class calling another Service class
Object-Oriented Design practice on Codemia
Turn requirements into classes, and defend the design, on the problems that come up in OOD rounds.
Introduction
Having one Spring service call another is normal and often desirable. The service layer exists to hold business rules, and real workflows usually combine multiple business capabilities such as billing, inventory, notifications, or auditing.
The important question is not whether a service may call another service, but how the dependency is wired and where the responsibility boundary lives. Good service composition keeps controllers thin, makes code easier to test, and avoids duplicated logic.
Why Service-to-Service Calls Are Common
A controller should usually translate HTTP input into an application request and then hand work off to the service layer. Once the request reaches the service layer, it is common for one service to delegate part of the workflow to another specialized service.
An order flow is a simple example. OrderService may validate the purchase, ask PaymentService to authorize a charge, then store the result through a repository. That is cleaner than placing payment logic directly inside the controller or duplicating it across several services.
The key idea is cohesion. Each service should own one area of business logic:
- '
OrderServicecoordinates order placement.' - '
PaymentServiceknows how to authorize or capture payments.' - '
EmailServicesends notifications.'
When responsibilities are separated this way, each class stays focused and reusable.
Wiring Services with Constructor Injection
The recommended pattern is constructor injection. Spring creates both beans and injects the dependency automatically.
This works because both classes are Spring-managed beans. You do not call new PaymentService() yourself. If you instantiate the dependency manually, Spring cannot apply bean lifecycle management, configuration, proxies, or transaction handling.
Constructor injection also improves tests because the dependency is explicit. You can supply a mock or fake implementation without spinning up the whole container.
Designing Clear Boundaries
Calling another service should represent a real dependency in the domain. If OrderService needs payment authorization, a call to PaymentService makes sense. If two services are constantly calling each other, the design is usually signaling a missing abstraction.
For example, if OrderService and InvoiceService both contain overlapping billing steps, that shared logic probably belongs in a dedicated BillingService. Extracting the common behavior reduces duplication and prevents circular dependencies.
It is also worth deciding whether a service should orchestrate a workflow or implement a single rule. Coordination logic belongs in an application service, while low-level business rules often belong in smaller specialized services.
Common Pitfalls
- Creating dependencies with
newinstead of injection. That bypasses Spring and breaks features like proxy-based transactions. - Using field injection everywhere. Constructor injection is easier to test and makes required dependencies obvious.
- Accidentally creating circular dependencies such as
A -> B -> A. If that happens, move the shared behavior into a third service. - Assuming every call shares the same transaction behavior.
@Transactionalworks through Spring proxies, so self-invocation inside the same class behaves differently from a call that crosses bean boundaries. - Letting controllers grow business logic because a service call feels inconvenient. That usually leads to duplicated rules and harder testing.
Summary
- One Spring service calling another is a standard pattern when responsibilities are well separated.
- Prefer constructor injection so Spring can manage the dependency correctly.
- Keep services cohesive: orchestration in one place, specialized rules in another.
- Use tests with mocks to verify service collaboration without starting the full application.
- If service calls become tangled, refactor toward clearer boundaries instead of adding more coupling.
Related reading
- Spring Boot 2 - Unsatisfied dependency on Feign client when autowired for service
- Spring Boot autowire beans from library project
- Spring Boot autowired does not work, classes in different package
- Spring boot autowiring an interface with multiple implementations
- Spring Boot - Validations stopped working after upgrade from 2.2.5 to 2.3.0
- Spring Boot - Wait for web server to start
- Spring Boot can't autowire ConfigurationProperties
- Spring Boot Inherit application.properties from dependency

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Object-Oriented Design practice on Codemia
Turn requirements into classes, and defend the design, on the problems that come up in OOD rounds.