Manual Mapping

A Hand-Written Mapper

The simplest mapper is a class with static (or instance) methods. It is plain Java, easy to read and easy to debug. This program runs: it shows the entity with internal fields, and the DTO that the client actually sees.

Example

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        var request = new CreateProductRequest("Wireless Mouse", "2.4 GHz, silent clicks",
                new BigDecimal("799.00"), 120, "Electronics");

        Product entity = ProductMapper.toEntity(request);
        entity.id = 1L;                                   // set by the database
        entity.costPrice = new BigDecimal("520.00");      // internal only
        entity.createdOn = LocalDate.of(2026, 10, 12);

        System.out.println("Entity:   " + entity);
        System.out.println("Response: " + ProductMapper.toResponse(entity));

        Product soldOut = new Product();
        soldOut.id = 2L; soldOut.name = "Clean Code"; soldOut.price = new BigDecimal("450.00");
        soldOut.stock = 0; soldOut.category = "Books";
        List<ProductResponse> page = List.of(entity, soldOut).stream().map(ProductMapper::toResponse).toList();
        page.forEach(r -> System.out.println("List item: " + r.name() + ", inStock=" + r.inStock()));
    }
}

class Product {                       // JPA entity in a real app
    Long id;
    String name, description, category;
    BigDecimal price, costPrice;
    int stock;
    LocalDate createdOn;

    public String toString() {
        return "Product[id=" + id + ", name=" + name + ", price=" + price + ", costPrice=" + costPrice
                + ", stock=" + stock + ", createdOn=" + createdOn + "]";
    }
}

record CreateProductRequest(String name, String description, BigDecimal price, int stock, String category) {}

record ProductResponse(Long id, String name, BigDecimal price, int stock, String category, boolean inStock) {}

final class ProductMapper {
    private ProductMapper() {}

    static Product toEntity(CreateProductRequest r) {
        Product p = new Product();
        p.name = r.name().trim();
        p.description = r.description();
        p.price = r.price();
        p.stock = r.stock();
        p.category = r.category();
        return p;
    }

    static ProductResponse toResponse(Product p) {
        return new ProductResponse(p.id, p.name, p.price, p.stock, p.category, p.stock > 0);
    }
}

Output

Entity:   Product[id=1, name=Wireless Mouse, price=799.00, costPrice=520.00, stock=120, createdOn=2026-10-12]
Response: ProductResponse[id=1, name=Wireless Mouse, price=799.00, stock=120, category=Electronics, inStock=true]
List item: Wireless Mouse, inStock=true
List item: Clean Code, inStock=false

Mapping an Update onto an Existing Entity

For PUT/PATCH you usually load the entity, then copy the request onto it, instead of creating a new object. That keeps fields the client does not control (id, createdOn, costPrice).

static void updateEntity(UpdateProductRequest r, Product target) {
    target.setName(r.name());
    target.setDescription(r.description());
    target.setPrice(r.price());
    target.setStock(r.stock());
    target.setCategory(r.category());
    // id, costPrice, createdOn are NOT touched
}

Exercise: Write toResponse for an entity that has firstName and lastName, where the DTO has a single fullName field.

Show answer
static UserResponse toResponse(User u) {
    return new UserResponse(u.getId(), u.getFirstName() + " " + u.getLastName(), u.getEmail());
}