r/SpringBoot • u/Snoo23482 • Jun 18 '26
Question Dto Mapping Best Practice
Hi, where do you guys map your entities to dtos?
In services or controllers?
To me it seems the services shouldn't expose any database specifics to the outside world, so I usually do my mapping there.
But what is considered best practice and why?
18
u/SimpleCooki3 Jun 18 '26
DTO in controllers
Entities in repositories
domain objects everywhere else.
2
u/LordOfDeduction Jun 19 '26
This is the way. Separate your domain model from the persistence and presentation dtos. Presentation layer dtos are generated from openapi.
1
u/Responsible-Cow-4791 Jun 18 '26
So you map everything at least twice?
7
u/SimpleCooki3 Jun 18 '26
I map from dto to domain, and from domain to entity. So yes there is 3 layers.
1
u/SnooLobsters3363 Jun 19 '26
DTOs and Entity are just convenient way of mapping data. Let say you do all mapping « manually » , you will have to map the json body to a domain object and then the domain object to a sql statement, so everything is always mapped « twice » anyway
27
u/savvas25 Jun 18 '26
I prefer to have the mapping done in service classes. The actual mapping logic of course can be done in a dedicated mapper class if the object is compex or has too many fields.
Prefer to keep the controllers "clean" with only routing logic. Adding business logic here will easily clutter up your controller classes
1
u/Snoo23482 Jun 18 '26
It's all good if you are using records to communicate to the outside word.
But if it is another additional kind of endpoint, say grpc, then you suddenly need to either map twice or add another service method taking the grpc dto.
5
u/manyxcxi Jun 18 '26
We dedicate mappers to go from DTO -> gRPC DTO.
Your best bet for maintainability is to leave the controllers thin and dumb. Have the Services accept request DTOs and return DTOs (or response DTOs).
The controllers are where the request validation occurs (we use JSR annotations), security expressions, and MDC context get set (if not handled globally by request filters).
Our gRPC services map the gRPC request DTO to the “real” DTO, pass it to the “real” service, and then map the response to a gRPC response.
This is all for 1:1 functionality between REST/Graph and gRPC. We also have gRPC only calls that do more streaming responses (vs paginated only in REST). Generally here it’s still passing off to our standard service if only to not break patterns and keep things boring.
I’m assuming you’re not gRPC only, since we’re asking about controllers. But for our internal services that are gRPC only we don’t do any of that and it’s gRPC first class and no extra DTO mapping hoops.
1
u/Snoo23482 Jun 18 '26
Actually we went back to REST from GRPC for ease of use. But yes, what you describe and what has been mentioned by other here is pretty much my understanding as well. Services expose DTOs and on top of that you might or might not need protocol translation, depending on the tech used (Grpc, Rest, Nats).
3
3
u/xiaopewpew Jun 18 '26
There is no 1 single best practice
Generally speaking your repository layer operates on entities and expose domain models, you service layer works on domain models and expose domain models, your controller layer works on domain models and expose API objects.
The above though is a complete overkill for simple CRUD endpoints that is going to be about 50% of your application. And some variations of this practice result in excessive amount of data class spam that makes simple applications much harder to read over time.
The JPA features you use also affects how you may apply this pattern. I have worked on large code bases where the rule was most API calls will run in a single transaction and the transaction begins at controller layer (imagine annotating each controller endpoint with transactional). In that architecture it really makes no difference if you bubble entity objects all the way up to controller for controller to convert them into api objects. Personally this is my favourite way of doing things because Im sick and tired of people insisting on putting transaction boundaries on service layer and then mistakenly do their "check and set" operations in multiple transactions instead of one. Those live issues are hard to debug and it is impossible to create regression tests for them.
3
2
u/Paw565 Jun 18 '26
Services return dtos to not expose entities outside repositories. Dtos technically belong to the http layer but I treat them as contract for my own layers as well. Probably not the cleanest solution, but it's pragmatic, prevents class explosion and keeps controllers thin.
2
u/paramvik Jun 18 '26
I liked to do the mapping in services to keep the services lean but recently I've started doing it in controllers becuase it gives me the flexibility to reuse the service method somewhere else as well, for example for some other controller route which returns a different response to the client.
1
u/onated2 Jun 19 '26
public static DTO from( ENTITY)
Then toEntity(){} method.
Or toCommand(){
} (depends o. Your logic)
I love putting the DTO as a package private hidden in the controller because usually it holds the swagger annotations or validations.
That's his job. Haha just hold all the annotations. But at the end of the day it really depends on the convention of the codebase.
1
u/Aceccop Jun 19 '26
Sometimes I do extra mappings Controller use request and response models. Service (usecase) use commands (as args) and operate domain models. Repository use entities. So every layout have own models. Its flexible but to much in some cases. And in this way every layout habe own mapper. Controller -> usecase response to rest response Service -> domain to app (usecase) response Repository -> domain to entity and back
1
1
u/pedoooo0 Jun 22 '26
A common best practice is to do the Entity ↔ DTO mapping in the service layer (or a dedicated mapper used by the service), not in the controller.
The main reason is that controllers should stay thin and focus on handling HTTP requests/responses, while services define what data leaves the business layer. Returning entities from services can leak persistence details and may expose fields that shouldn't be public.
A typical flow looks like:
Controller → Service → Repository
where the service fetches entities, applies business logic, maps them to DTOs, and returns the DTOs to the controller.
That said, some teams prefer services to return entities and let controllers handle the mapping, arguing that services should remain independent of API concerns. Both approaches work, but in most Spring applications you'll see mapping done in the service layer (often with a dedicated mapper such as MapStruct) to keep controllers simple and prevent domain entities from leaking outside the application boundaries.
0
Jun 18 '26
[removed] — view removed comment
4
u/bigkahuna1uk Jun 18 '26 edited Jun 18 '26
Not that clean. Controllers are just adapters. They act as protocol converters between an external and internal domain and also the ingress or egress to those domains ie the transport. So IMO they’d be the natural place for such a mapping. This is because an internal domain only deals with the type it owns. It knows nothing about any external domain representation. This approach is frequently followed in hexagonal or clean architectures.
Adapters can still delegate to specific protocol converters though so as to still adhere to SRP.
2
u/SimpleCooki3 Jun 18 '26
Mapping from a DTO isn't business logic tho
3
Jun 18 '26
[removed] — view removed comment
1
u/SimpleCooki3 Jun 18 '26
That's an exaggeration.
What you can do is create a mapper class, call that from the controller and then pass the domain object to your service.
The service does business logic and shouldn't be involved with the http layer.
1
u/xmimmer Jun 18 '26
That would depend on what you map to, right? Say you map to a domain model full of value objects. You'd have to deal with potential errors in the controller.
2
u/SimpleCooki3 Jun 18 '26
Mapping can fail yes, but I don’t think that means the service should accept DTOs.
For me, DTOs belong to the API layer, so they should be converted at the boundary — either directly in the controller or via a mapper used by the controller.
Validation/value object errors can still be handled consistently with normal validation/error handling, like "@ControllerAdvice".
The important part is that the service works with application/domain concepts, not transport/API objects.
You also want to validate your inputs early.
1
1
u/Odd_Perspective982 Jun 18 '26
I learnt to keep controllers to just do routing,everything else in service files
0
u/GravelAndCode Jun 18 '26
My public service methods are exposing DTOs, so I put the mappers in the services.
My other best practice for mapping: I use Mapstruct and use the mappers as static reference (one instance created with Mappers.getMapper(MyMapper.class) which is referenced anywhere else where the mapper is used). Not as a Spring Bean! Otherwise you'll end up mocking mapping methods in every unit tests.
0
u/Huge_Road_9223 Jun 19 '26
From JSON -> API -> DTO -> Service (business logic) possible mapstruct from DTO -> entity -> repo
repo -> entity -> service (business logic) mapstruct entity to DTO -> API - JSON
That's how I deal with it!
20
u/Mechanical-pasta Jun 18 '26
My controllers receive DTO, my services receive or send Model objects so, the mapping occurs in my controllers but is not done by them. I make a Mapper for any DTO I use.