java 112 lines · 4 tabs

Mapping JPA Entities to DTOs with a MapStruct Mapper Injected into a Spring Service

Shared by codesnips Jul 2026
4 tabs
package com.example.users.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.Instant;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String firstName;

    @Column(nullable = false)
    private String lastName;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false, updatable = false)
    private Instant createdAt = Instant.now();

    public Long getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String getEmail() {
        return email;
    }

    public Instant getCreatedAt() {
        return createdAt;
    }
}
4 files · java Explain with highlit

This snippet shows the standard way a Spring Boot application separates its persistence model from its API contract using MapStruct, a compile-time bean mapping library. Instead of hand-writing tedious entity.getX() / dto.setX() conversion code, MapStruct generates the implementation at build time, so there is no reflection cost at runtime and mapping bugs surface as compiler errors rather than production surprises.

The User entity is an ordinary JPA @Entity with a first and last name, an email, and a createdAt timestamp. It is the database shape and should never leak directly out of a controller, both because it exposes internal columns and because a serialized Hibernate entity can trigger lazy-loading or expose fields the client should not see. The UserDto record is the outward-facing shape: it flattens firstName and lastName into a single fullName and omits anything the API does not need.

The interesting piece is UserMapper. It is a plain interface annotated with @Mapper(componentModel = "spring"), which tells MapStruct to generate an implementation and register it as a Spring @Component so it can be autowired like any other bean. The @Mapping annotations describe the non-trivial transformations: expression builds fullName from two source fields, while dateFormat converts the Instant into a formatted string. Fields with matching names and types — such as email — are mapped automatically and need no configuration. A toDtoList method is declared so MapStruct also generates the collection-mapping loop.

In UserService, the generated mapper is injected via constructor injection alongside the UserRepository. The service loads entities, delegates all conversion to userMapper, and returns only DTOs, keeping mapping logic out of both the controller and the persistence layer. This is the key trade-off MapStruct optimizes for: a small annotation-processing setup and a strict compile step in exchange for fast, type-safe, boilerplate-free mapping. A common pitfall is forgetting the annotation processor in the build or referencing a field name that no longer exists, both of which fail the compile rather than silently producing nulls.


Related snips

Share this code

Here's the card — post it anywhere.

Mapping JPA Entities to DTOs with a MapStruct Mapper Injected into a Spring Service — share card
Link copied