Author: vmf

  • The Beyond Path

    The Beyond Path

    A step into the unseen

    I was sleeping deeply when I heard my dogs barking. Startled, I woke up and listened. Then I heard something else—the front door opening. My heart started racing. I got up quietly, ready to confront whoever was coming in. I stayed still, waiting to catch the intruder by surprise.

    A short man stepped inside. It was too dark to see his face. I clenched my fists, ready to throw a punch—but suddenly, I woke up.

    Or so I thought.

    Everything felt… off. The room looked like mine, but slightly wrong. I got out of bed and walked into the hallway. It was my home, but the longer I walked, the stranger it got. Door after door after door—until I reached one that led to complete blackness.

    Pitch dark. A void.

    But something about it felt familiar. I’d been here before, I was sure of it. So I took a breath and stepped in.

    The darkness swallowed me whole.

    I kept walking, but the ground felt like it vanished. I began to float, or maybe swim—it felt like diving into a deep, endless ocean. Above me, the ceiling shimmered like water, and I tried to surface, but something pulled me down, kept me from rising.

    So I swam. And swam. And swam.

    Then I saw it.

    A massive creature—like a squid, but twisted, demonic. It had four glowing eyes and an aura of dread. As I got closer, it launched fireballs at me.

    But I wasn’t powerless.

    With my right hand, I hurled fireballs back at it. With my left, I summoned lightning bolts that cracked through the darkness. The battle was fierce, but I struck the final blow. The creature exploded like something out of a video game, bursting into glowing cards that scattered and vanished into the void.

    The path ahead opened.

    And I followed it.

  • Escape the If Jungle

    Escape the If Jungle

    Modern Conditional Strategies in Java

    Classic Conditionals:

    1. if
    2. if else
    3. if - else if - else
    4. switch
    5. Ternary operator (condition ? a : b)

    Advanced or Alternative Structures:

    1. Map<K, Runnable> or Map<K, Function> – to simulate switch/if chains for behavior
    2. enum with behavior (Strategy Pattern) – enums can define a method differently for each constant
    3. Polymorphism – define different implementations for an interface instead of conditional logic
    4. Command Pattern – store commands in a map or list and execute dynamically
    5. Chain of Responsibility Pattern – each handler decides whether to handle or pass along
    6. Reflection – dynamically invoke methods (not recommended for everyday logic, but possible)
    7. Pattern Matching for switch (Java 21+) – powerful new switch functionality with types

    Now, let’s write a class that demonstrates a few of the more interesting and non-obvious ones: using enum with behavior, map of commands, and polymorphism.

    📦 ConditionalStructuresShowcase.java

    import java.util.HashMap;
    import java.util.Map;
    import java.util.function.Consumer;
    
    public class ConditionalStructuresShowcase {
    
        // Using Enum with Strategy Pattern
        enum Operation {
            ADD {
                @Override
                int apply(int a, int b) {
                    return a + b;
                }
            },
            SUBTRACT {
                @Override
                int apply(int a, int b) {
                    return a - b;
                }
            },
            MULTIPLY {
                @Override
                int apply(int a, int b) {
                    return a * b;
                }
            },
            DIVIDE {
                @Override
                int apply(int a, int b) {
                    return b != 0 ? a / b : 0;
                }
            };
    
            abstract int apply(int a, int b);
        }
    
        // Using Map of commands
        private final Map<String, Runnable> commandMap = new HashMap<>();
    
        public ConditionalStructuresShowcase() {
            commandMap.put("hello", () -> System.out.println("Hello there!"));
            commandMap.put("goodbye", () -> System.out.println("Goodbye, friend!"));
            commandMap.put("surprise", () -> System.out.println("🎉 Surprise! 🎉"));
        }
    
        public void executeCommand(String command) {
            commandMap.getOrDefault(command, () -> System.out.println("Unknown command")).run();
        }
    
        // Using Polymorphism to avoid if-else
        interface Animal {
            void speak();
        }
    
        static class Dog implements Animal {
            public void speak() {
                System.out.println("Woof!");
            }
        }
    
        static class Cat implements Animal {
            public void speak() {
                System.out.println("Meow!");
            }
        }
    
        static class NinjaTurtle implements Animal {
            public void speak() {
                System.out.println("Cowabunga!");
            }
        }
    
        public static void main(String[] args) {
            ConditionalStructuresShowcase showcase = new ConditionalStructuresShowcase();
    
            // Example: enum with strategy
            System.out.println("Enum ADD: " + Operation.ADD.apply(5, 3));
            System.out.println("Enum DIVIDE: " + Operation.DIVIDE.apply(10, 2));
    
            // Example: map of commands
            showcase.executeCommand("hello");
            showcase.executeCommand("surprise");
            showcase.executeCommand("not-a-command");
    
            // Example: polymorphism
            Animal dog = new Dog();
            Animal cat = new Cat();
            Animal turtle = new NinjaTurtle();
    
            dog.speak();
            cat.speak();
            turtle.speak();
        }
    }
    

    This class shows three strong alternatives to conditionals:

    • Replace complex switch logic with enums + abstract methods.
    • Use a map of commands to handle input-based logic.
    • Apply polymorphism instead of big if chains to choose behavior based on object type.

    The java project can be found here.

  • Dealing with the torturer

    Dealing with the torturer

    The Pact with Tractor Torturer

    The four ninjas walked in silence, the sound of their footsteps swallowed by the dark corridors that led to the chamber of Tractor Torturer. He was a giant of legend — feared, brutal, and impossible to ignore. They had struck a deal with him. Not good, not bad. Just… a deal. A means to an end. Something that would move their mission forward, despite the risk. Despite his terrible fame.

    Among the four, there was one — a man whose thoughts rarely aligned with logic. A bit slow, often clueless, but always unpredictable. And that made him dangerous. He had a plan, one he hadn’t shared with anyone. While the others negotiated, he scanned the room and saw the massive chains embedded in the stone wall — chains meant to bind even the strongest.

    A thought slithered into his mind: What if I lured Tractor Torturer near the wall? What if I chained him up? What if I ended him right here?

    So he tried. He acted alone. Recklessly.

    The moment came. Tractor Torturer, amused and unaware, was drawn toward the wall. The ninja moved to strike — but froze. As if his courage was nothing more than a whisper in the face of the storm.

    It was then the leader stepped forward.

    No hesitation.

    He launched a brutal punch straight to the idiot’s face — then another, and another. It wasn’t rage. It was survival. He had to act fast, show submission, punish the fool before Tractor Torturer did something far worse.

    The punches landed like thunder.

    And when Tractor Torturer finally chuckled — pleased by the display — the leader stopped. Blood dripped. Silence returned.

    The pact was still alive.

    But trust among the ninjas?

    That was starting to break.

  • 📨 Modern Messaging with Spring Boot and ActiveMQ

    📨 Modern Messaging with Spring Boot and ActiveMQ

    Why Messaging Matters in Modern Applications

    In a distributed system, services often need to communicate asynchronously to ensure decoupling, scalability and resilience. Messaging systems enable this by allowing services to send and receive messages without needing to know about each other’s existence or availability in real time. This pattern is crucial in microservices, event-driven architectures and systems requiring reliable data exchange.

    What is Apache ActiveMQ Artemis?

    Apache ActiveMQ Artemis is a high-performance, embeddable, multi-protocol message broker. It’s the next-generation version of ActiveMQ, designed to support modern messaging needs with improved performance, scalability and features like:

    • Support for JMS 2.0 API
    • Core messaging protocol (AMQP, MQTT, STOMP, OpenWire)
    • High throughput and low latency
    • Built-in clustering and persistence

    Why Use ArtemisMQ with Spring Boot?

    Spring Boot simplifies the setup and integration of messaging with ArtemisMQ through auto-configuration, sensible defaults and tight JMS support. Combined, they provide:

    • Rapid development with minimal boilerplate
    • Easy testing with embedded brokers
    • Seamless JSON serialization with MessageConverter
    • Robust error handling and observability with Spring Boot Actuator

    Project Setup

    pom.xml Dependencies

    Here are the essential dependencies to get started:

    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-artemis</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>  <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>org.apache.activemq</groupId>
        <artifactId>artemis-jms-server</artifactId>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>org.awaitility</groupId>
        <artifactId>awaitility</artifactId>
        <scope>test</scope>
      </dependency></dependencies>
    

    Embedded ArtemisMQ Setup

    Spring Boot can auto-configure an embedded Artemis server when no external broker is specified. Just add this to your application.properties:

    spring.artemis.mode=embedded
    spring.artemis.embedded.enabled=true

    This allows you to run and test your messaging app without external infrastructure.

    The embedded Artemis isn’t suitable for production, since it lacks some important Artemis features, but it’s great for connecting to a ActiveMQ server and testing. But in this example we will be using the embedded to demonstrate its capabilities.

    ❌ Embedded Artemis (Spring Boot auto-config)

    Good for:

    • Local development
    • Demos and testing
    • Simpler projects with no broker deployment needs

    Bad for:

    • Production use
    • Scalability and clustering
    • Monitoring and management
    • Reliability (you lose messages if the app crashes)

    Embedded Artemis runs in-process, so if your Spring Boot app crashes or restarts, your broker goes down too—and takes your messages with it.


    Creating a Simple Messaging Flow

    1. Define a DTO

    public class NotificationMessage {
        private String title;
        private String body;
    
        // Getters and setters
    }
    

    2. Send Messages with JmsTemplate

    @Service
    public class NotificationSender {
    
        private final JmsTemplate jmsTemplate;
    
        public NotificationSender(JmsTemplate jmsTemplate) {
            this.jmsTemplate = jmsTemplate;
        }
    
        public void send(NotificationMessage message) {
            jmsTemplate.convertAndSend("notification.queue", message);
        }
    }
    

    3. Receive Messages with @JmsListener

    @Component
    public class NotificationReceiver {
    
        @JmsListener(destination = "notification.queue")
        public void receive(NotificationMessage message) {
            System.out.println("Received message: " + message.getTitle());
        }
    }
    

    4. Configure MessageConverter for JSON

    @Configuration
    public class MessagingConfig {
    
        @Bean
        public MessageConverter jacksonJmsMessageConverter() {
            MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
            converter.setTargetType(MessageType.TEXT);
            converter.setTypeIdPropertyName("_type");
            return converter;
        }
    }
    

    This ensures that your messages are automatically converted to/from JSON when using JmsTemplate or @JmsListener.


    ✅ Write the Integration Test

    @SpringBootTest
    class MessagingIntegrationTest {
    	
    	@SpringBootConfiguration
        @EnableAutoConfiguration
        @ComponentScan(basePackages = "vmfvmf.com")
        static class TestConfig {}
    
        @Autowired
        private NotificationSender sender;
        
        @SpyBean
        private NotificationReceiver receiver;
    
        @Test
        void testMessageSendAndReceive() {    	
            NotificationMessage message = new NotificationMessage();
            message.setTitle("Hello");
            message.setBody("This is a test message");
    
            sender.send(message);
            
            waitAtMost(2, TimeUnit.SECONDS).untilAsserted(() -> {
            	verify(receiver, times(1)).receive(argThat(argument -> argument.getBody().contentEquals("This is a test message")));
            });
            
        }
    }

    This test verifies that a NotificationMessage is successfully sent and received using Spring Boot, ActiveMQ Artemis, and JMS. It uses JmsTemplate to send the message and a @JmsListener-based receiver to consume it. The test leverages Mockito to verify the receiver was called with the expected content, and Awaitility to wait for the asynchronous processing to complete within a timeout, ensuring reliable behavior testing in a message-driven flow.

    In this case we had a success, the received message had the same content of the sent message, but if change the validation…

    We will got an error telling that the arguments are different.

    This test approach, using an embedded Artemis broker, is well-suited for local development and integration testing. It allows for fast, isolated, and repeatable tests without requiring an external broker. Combining Spring Boot, JMS, Awaitility, and Mockito provides a robust way to validate asynchronous messaging behavior in real time. However, while embedded Artemis is convenient for testing, it’s not recommended for production, as it doesn’t reflect a distributed, secured, or tuned broker environment. For production-grade scenarios, tests should also be validated against an external broker setup to ensure compatibility and performance under realistic conditions.


    ✅ Conclusion

    In this first part, we explored the importance of messaging in modern applications and introduced Apache ActiveMQ Artemis as a powerful broker solution. We saw how effortlessly it integrates with Spring Boot, offering a fast path to building robust messaging flows using JMS, JmsTemplate and @JmsListener. With just a few configurations and components, you can set up a fully working asynchronous communication channel between services—complete with JSON serialization out of the box.

    This foundation is not just functional—it’s production-ready with the right enhancements.


    🔜 What’s Next?

    In the next parts, we’ll level up the integration by diving into:

    • Custom error handling and how to gracefully manage message failures.
    • Retry strategies, dead-letter queues and avoiding message loss.
    • Delayed and priority messaging, giving you more control over how and when messages are processed.
    • Exposing messaging functionality through REST endpoints with springdoc-openapi, so your services are not just reactive—but also transparent and well-documented.
    • Testing strategies using dockerized Artemis and Spring Boot testing utilities.

    Stay tuned as we continue building a full-featured messaging layer that’s resilient, observable and ready for scale.

    Here is a LINK with the running code in case you couldn’t follow this tutorial and not managed to test.

  • Understanding One-to-One Relationships: Database vs Java (Hibernate)

    Understanding One-to-One Relationships: Database vs Java (Hibernate)

    When modeling One-to-One relationships, you can approach it in several ways depending on the ownership, lifecycle, and data modeling constraints. While Java and Hibernate offer flexibility in how this relationship is implemented at the code level, the root of it lies in database design. So let’s start there.


    One-to-One Relationship Strategies in Database Engineering

    In relational databases, a One-to-One relationship means each row in Table A corresponds to exactly one row in Table B, and vice versa. But there are multiple ways to implement this, depending on the direction and ownership of the relationship.

    1. Foreign Key in Parent Table (Shared Primary Key strategy – reversed)

    • How it works: The parent table (e.g., Person) holds a foreign key to the child table (Passport.id).
    • Pros:
      • Easy to query from parent.
      • Clear ownership.
    • Cons:
      • Implies that the parent cannot exist without the child — or needs NULL values.
      • Foreign key can be NULL if optional.
    CREATE TABLE person (
        id BIGINT PRIMARY KEY,
        name VARCHAR(100),
        passport_id BIGINT UNIQUE,
        FOREIGN KEY (passport_id) REFERENCES passport(id)
    );
    

    2. Child Table Uses Parent’s Primary Key (Shared Primary Key strategy)

    • How it works: The child table (e.g., Passport) uses the same ID as the parent (Person) — usually both primary key and foreign key.
    • Pros:
      • Enforces strict 1:1 relationship.
      • Cleaner data integrity — Passport cannot exist without Person.
    • Cons:
      • Less flexible if the child becomes optional.
      • Harder to manage outside ORMs.
    CREATE TABLE person (
        id BIGINT PRIMARY KEY,
        name VARCHAR(100)
    );
    
    CREATE TABLE passport (
        id BIGINT PRIMARY KEY,
        number VARCHAR(50),
        FOREIGN KEY (id) REFERENCES person(id)
    );
    

    3. Join Table Strategy (Less common for One-to-One)

    • How it works: A third table (e.g., person_passport) holds person_id and passport_id with a unique constraint.
    • Pros:
      • Very flexible and decoupled.
      • If the relation has a tendency to become 1xN or Nx1 or NxN then it’s a good choice because it will make the transition easier.
    • Cons:
      • Overkill unless mapping polymorphic or cross-entity relations.
      • High complexity for something that can be simple.

    Entity Mapping in Hibernate & Java

    Now let’s translate this to Java and Hibernate.

    Shared Primary Key Strategy (Child uses parent’s ID)

    @Entity
    public class Person {
        @Id
        private Long id;
    
        @OneToOne(mappedBy = "person", cascade = CascadeType.ALL)
        private Passport passport;
    }
    
    @Entity
    public class Passport {
        @Id
        private Long id;
    
        @OneToOne
        @MapsId
        @JoinColumn(name = "id")
        private Person person;
    
        private String number;
    }
    
    • @MapsId indicates that Passport shares the same primary key as Person.
    • Very tight coupling — used when lifecycle is tightly bound.

    Foreign Key in Parent Table

    @Entity
    public class Person {
        @Id
        private Long id;
    
        @OneToOne
        @JoinColumn(name = "passport_id")
        private Passport passport;
    }
    
    @Entity
    public class Passport {
        @Id
        private Long id;
        private String number;
    }
    
    • More flexible: Person can exist without Passport.
    • You control fetch/lazy, cascade, optionality more easily.
    • More flexible: Person can exist without Passport.
    • You control fetch/lazy, cascade, optionality more easily.

    What about property with JoinTables?

    Sometimes, your model may evolve and you’d rather keep the base entity clean and extend when needed. For example, instead of putting a Passport inside Person, you could have:

    @Entity
    public class Passport  {
    
        @OneToOne
        @JoinTable(
            name = "person_passport",
            joinColumns = @JoinColumn(name = "person_id"),
            inverseJoinColumns = @JoinColumn(name = "passport_id")
        )
        private Passport passport;
    }
    

    This approach uses:

    • Join Table in JPA.
    • @JoinTable allows modeling more flexible or optional relationships.
    • Useful when only some persons have passport.
    • A smart choice when there’s a possibility that the relationship’s cardinality may change.

    It’s especially powerful when you want to modularize behavior and avoid cluttering the base Person class but makes the entity mapping very complex.


    What About Inheritance with Joined Strategy?

    Sometimes you just want a easier way to navigate the entity properties and treat the two tables as a single entity. For example, instead of putting a Passport inside Person, you could extend it:

    @Entity
    @Inheritance(strategy = InheritanceType.JOINED)
    public class Person {
        @Column
        @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    }
    
    @Entity
    public class Passport extends Person {
    
       @Column
       private String number;
    }
    

    Summary

    • InheritanceType.JOINED allows you to split the data into separate tables for each class in the inheritance hierarchy.
    • The child class has its own table and its joined with the parent class, so the child entity has all properties from the parent as its own.
    • Hibernate will automatically handle the joins when querying entities from the inheritance hierarchy.

    This strategy is useful when you want to follow database normalization principles but be mindful of the performance overhead due to the necessary joins when querying data.


    Final Thoughts: Choosing the Right Strategy

    Your choice of strategy in the database layer directly impacts your entity mapping in Java:

    • Use Shared Primary Keys when the two entities are tightly bound and always created/removed together.
    • Use a Foreign Key in Parent when the child is optional or loosely coupled.
    • Use Join Tables or Inheritance when dealing with complex relationships, partial behaviors, or domain subtypes.

    Choosing the right one-to-one mapping isn’t just about the ORM — it’s about data consistency, flexibility, and how your domain actually behaves.

  • Does Garbage Collector Choice Matters for Microservices?

    When building and deploying microservices with Java — whether using Spring Boot or WildFly — one of the often-overlooked performance factors is the Garbage Collector (GC). Should you care about which GC your application uses? Or should you leave it to the infrastructure? Let’s break it down.

    Why Garbage Collector Choice Matters

    Garbage Collection is a core part of how the Java Virtual Machine (JVM) manages memory. The GC’s job is to clean up unused objects to free up memory, but how and when it does that can significantly impact your application’s performance.

    In microservices, where responsiveness, stability, and resource efficiency are crucial, the GC can be the silent bottleneck if not configured properly. Here’s why:

    1. Latency Spikes

    Some GCs (like Parallel GC) may cause long application pauses. These pauses can increase response times, break SLAs, or even cause failures in health checks — especially in Kubernetes environments.

    2. Throughput vs. Responsiveness

    Different GCs optimize for different things. For example:

    • Parallel GC prioritizes throughput (processing as much as possible).
    • G1GC, ZGC, and Shenandoah focus on minimizing pause times — ideal for services handling frequent API requests.

    3. Memory Efficiency

    In containerized deployments, memory is usually limited. A GC that can handle memory fragmentation and pressure gracefully (like G1GC) is often a better fit.

    4. Startup Time

    In some microservices that scale quickly (e.g., under load), faster startup may matter. In those cases, GC behavior can also influence how fast a new instance becomes ready.


    Popular GC Options (Java 21)

    GCPause TimeThroughputNotes
    G1GC (default)LowHighGood general-purpose choice for microservices.
    ZGCVery LowModerateDesigned for ultra-low pause times, works well with large heaps.
    ShenandoahVery LowModerateSimilar to ZGC, especially effective in Red Hat-based environments.
    Parallel GCHighVery HighGood for batch processing, not ideal for APIs.
    Serial GCHighLowOnly suitable for tiny apps or short-lived processes.

    Spring Boot vs. WildFly: Who Chooses the GC?

    Spring Boot

    You have full control over the JVM. Whether you run it as a fat JAR or inside a Docker container, you decide the GC strategy. You can pass GC options via command line, JAVA_OPTS, or Dockerfile.

    WildFly

    WildFly (Jakarta EE) runs inside a managed JVM. But even here, you can (and should) configure the GC by editing the standalone.conf file or Docker entrypoint.

    Cloud & Kubernetes Deployments

    In containerized environments, infrastructure often sets memory limits, but it’s still your responsibility to:

    • Configure the GC explicitly.
    • Set memory-aware options like -XX:MaxRAMPercentage.
    • Monitor and tune based on GC logs (-Xlog:gc* in Java 21+).

    Best Practices

    • Stick with G1GC unless you have specific latency or heap size needs.
    • Consider ZGC or Shenandoah for low-latency, large-memory services.
    • Avoid Parallel GC in APIs unless it’s a batch job.
    • Always monitor GC logs in production.
    • Tune JVM settings for container memory limits.

    Final Thoughts

    In microservices, small inefficiencies can scale into big problems. GC choice might seem like a low-level detail, but it has a direct impact on the performance, stability, and scalability of your services.

    So yes — whether you’re using Spring Boot, WildFly, or any other Java stack, take a few minutes to understand and configure your Garbage Collector. Your uptime (and your users) will thank you.

  • Seems So…

    Seems So…

    Echos from the other side of the crash

    It was a very cold and rainy night, and the streets were as slippery as soap. I was driving when my car began to skid. I stopped accelerating, trying to regain control, but the car kept sliding, swerving from one side of the street to the other. I saw an electric fence nearby and panicked, afraid the car might crash into it. But it kept sliding further down the road.

    Then, I saw the end of the street—just ahead was a steep precipice, maybe 20 meters high. The car didn’t stop. We went over the edge and fell.

    After the impact, I checked myself—I was okay. I got out of the car, and two men approached me. One of them asked, “Are you alive?”

    I replied, “Seems so.”

    I walked toward a nearby mansion as a single-engine plane passed overhead. Outside, I saw a man and his wife who looked like newlyweds. I went inside the mansion, and there was another man about to take his dog for a walk. But this was no ordinary dog—it was a monster. I watched as he fed it what looked like a fresh human femur.

  • Mastering MapStruct

    Mastering MapStruct

    @Mapping, @AfterMapping, and @MappingTarget in Action

    🧩 Introduction

    In modern Java applications, especially with frameworks like Spring Boot, object mapping is a common task. Whether you’re transforming DTOs into entities or mapping between domain layers, doing this manually is tedious and error-prone.

    MapStruct is a compile-time code generator that simplifies this task—fast, type-safe, and easy to use. In this article, we’ll explore three powerful annotations that give you more control and flexibility:

    • @Mapping
    • @AfterMapping
    • @MappingTarget

    🔧 Setting the Stage: Basic Setup

    Let’s assume we have a simple Spring Boot project with MapStruct configured.

    Dependencies (Maven):

    <properties>
     <java.version>21</java.version>
     <mapstruct.version>1.5.5.Final</mapstruct.version>
     <mvn.compile.version>3.11.0</mvn.compile.version>
    </properties>
    
    <dependency>
      <groupId>org.mapstruct</groupId>
      <artifactId>mapstruct</artifactId>
      <version>${mapstruct.version}</version>
    </dependency>
    <dependency>
      <groupId>org.mapstruct</groupId>
      <artifactId>mapstruct-processor</artifactId>
      <version>${mapstruct.version}</version>
      <scope>provided</scope>
    </dependency>
    
    <!-- and in build tag plugins -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>${mvn.compile.version}</version>
      <configuration>
        <annotationProcessorPaths>
          <path>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${mapstruct.version}</version>
          </path>
          <!-- Other processors like Lombok can go here -->
        </annotationProcessorPaths>
      </configuration>
    </plugin>

    Make sure annotation processing is enabled in your IDE or build tool.


    ✅ Example Scenario: Mapping User DTO to Entity

    Let’s say you have:

    UserDTO.java

    public record UserDTO(String firstName, String lastName, String email) {}
    User.java
    public class User {
        private String fullName;
        private String email;
        private LocalDateTime createdAt;
        // getters & setters
    }

    🎯 Using @Mapping to Customize Field Mapping

    By default, MapStruct matches fields by name. But when the names don’t match (like firstName + lastNamefullName), you can use @Mapping.

    UserMapper.java

    @Mapper
    public interface UserMapper {
    
        @Mapping(target = "fullName", expression = "java(dto.firstName() + \" \" + dto.lastName())")
        @Mapping(target = "createdAt", ignore = true) // we’ll handle this later
        User toEntity(UserDTO dto);
    }
    

    Here:

    • expression = "java(...)” lets you use Java code directly for custom logic.
    • ignore = true tells MapStruct to skip mapping that field.

    🧪 Enhancing Mapping with @AfterMapping

    Let’s say we want to set createdAt to the current time. This is where @AfterMapping comes in handy.

    @Mapper
    public interface UserMapper {
    
        @Mapping(target = "fullName", expression = "java(dto.firstName() + \" \" + dto.lastName())")
        @Mapping(target = "createdAt", ignore = true)
        User toEntity(UserDTO dto);
    
        @AfterMapping
        default void setCreatedAt(@MappingTarget User user) {
            user.setCreatedAt(LocalDateTime.now());
        }
    }
    
    • @AfterMapping lets you customize the result after the auto-mapping is done.
    • @MappingTarget gives you access to the mapped target object.

    🔁 Reusing Objects with @MappingTarget

    Suppose you’re updating an existing User entity instead of creating a new one:

    @Mapper
    public interface UserMapper {
    
        @Mapping(target = "fullName", expression = "java(dto.firstName() + \" \" + dto.lastName())")
        @Mapping(target = "createdAt", ignore = true)
        void updateEntity(UserDTO dto, @MappingTarget User user);
    }
    
    • This avoids creating a new object.
    • Great for PATCH/update endpoints where you fetch an entity from the DB, then update some fields.

    🧠 Wrap-Up

    These three annotations are the foundation of writing powerful, flexible, and clean mappers with MapStruct:

    AnnotationPurpose
    @MappingCustomize individual field mapping
    @AfterMappingAdd post-processing logic
    @MappingTargetModify an existing object instead of creating a new one

    Mastering these tools helps you avoid boilerplate, improve readability, and maintain a clean domain model.


    📦 Bonus: When Should You Use These?

    • Use @Mapping when field names/types differ or when you want custom logic.
    • Use @AfterMapping for audit fields (createdAt, updatedBy) or conditional logic.
    • Use @MappingTarget when updating existing entities (e.g., in services).
  • The met word

    The met word

    A Moment in Heaven

    I was in Heaven, drifting through the light,
    when I encountered a book —
    a holy book, suspended in the sky.
    Drawn by its radiance, I floated closer,
    my breath caught in awe at its perfection.

    I tried to read…
    but the words blurred like smoke,
    just out of reach,
    as if they were meant for eyes beyond my knowing.

    Page after page, I turned,
    desperate to find a single truth I could hold.
    But nothing.
    Then—darkness.
    The light began to fade.
    I was being pulled away.

    I realized then:
    God was removing me from the book’s presence.
    And so I cried out,
    “Please, God…
    Let me stay a moment more.
    Don’t take me yet.
    I need to find something.”

    And in mercy, He granted it.
    Time stood still.
    And within that breath of grace,
    I saw it—
    my word.