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;
}
}
package com.example.users.api;
public record UserDto(
Long id,
String fullName,
String email,
String memberSince
) {
}
package com.example.users.api;
import com.example.users.domain.User;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface UserMapper {
@Mapping(
target = "fullName",
expression = "java(user.getFirstName() + \" \" + user.getLastName())"
)
@Mapping(target = "memberSince", source = "createdAt", dateFormat = "yyyy-MM-dd")
UserDto toDto(User user);
List<UserDto> toDtoList(List<User> users);
}
package com.example.users.service;
import com.example.users.api.UserDto;
import com.example.users.api.UserMapper;
import com.example.users.domain.User;
import com.example.users.repository.UserRepository;
import jakarta.persistence.EntityNotFoundException;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserRepository userRepository;
private final UserMapper userMapper;
public UserService(UserRepository userRepository, UserMapper userMapper) {
this.userRepository = userRepository;
this.userMapper = userMapper;
}
@Transactional(readOnly = true)
public List<UserDto> findAll() {
return userMapper.toDtoList(userRepository.findAll());
}
@Transactional(readOnly = true)
public UserDto findById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found: " + id));
return userMapper.toDto(user);
}
}
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
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
GraphQL API with Spring Boot
package com.example.myapp
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
Dependency injection with Hilt
package com.example.starter.config;
import com.example.starter.properties.CustomProperties;
import com.example.starter.service.CustomService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
Custom Spring Boot starters
<?php
namespace App\Providers;
use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;
Laravel service container and dependency injection
package com.example.demo.config;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
Messaging with Apache Kafka
module Paginatable
extend ActiveSupport::Concern
MAX_PER_PAGE = 100
DEFAULT_PER_PAGE = 25
API Pagination Headers (Link + Total)
Share this code
Here's the card — post it anywhere.