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.
Map<K, Runnable> or Map<K, Function> – to simulate switch/if chains for behavior
enum with behavior (Strategy Pattern) – enums can define a method differently for each constant
Polymorphism – define different implementations for an interface instead of conditional logic
Command Pattern – store commands in a map or list and execute dynamically
Chain of Responsibility Pattern – each handler decides whether to handle or pass along
Reflection – dynamically invoke methods (not recommended for everyday logic, but possible)
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 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.
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:
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:
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.
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.
@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.
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)
GC
Pause Time
Throughput
Notes
G1GC (default)
Low
High
Good general-purpose choice for microservices.
ZGC
Very Low
Moderate
Designed for ultra-low pause times, works well with large heaps.
Shenandoah
Very Low
Moderate
Similar to ZGC, especially effective in Red Hat-based environments.
Parallel GC
High
Very High
Good for batch processing, not ideal for APIs.
Serial GC
High
Low
Only 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.
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.
@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 + lastName → fullName), 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.
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.