Partner – Orkes – NPI EA (cat=Spring)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Partner – Orkes – NPI EA (tag=Microservices)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Partner – LambdaTest – NPI EA (cat=Testing)
announcement - icon

Browser testing is essential if you have a website or web applications that users interact with. Manual testing can be very helpful to an extent, but given the multiple browsers available, not to mention versions and operating system, testing everything manually becomes time-consuming and repetitive.

To help automate this process, Selenium is a popular choice for developers, as an open-source tool with a large and active community. What's more, we can further scale our automation testing by running on theLambdaTest cloud-based testing platform.

Read more through our step-by-step tutorial on how to set up Selenium tests with Java and run them on LambdaTest:

>> Automated Browser Testing With Selenium

Partner – Orkes – NPI EA (cat=Java)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

1. Overview

In this tutorial, we’ll explain how to use @DomainEvents annotation and AbstractAggregateRoot class to conveniently publish and handle domain events produced by aggregate – one of the key tactical design patterns in Domain-driven design.

Aggregates accept business commands, which usually results in producing an event related to the business domain – the Domain Event.

If you’d like to learn more about DDD and aggregates, it’s best to start with Eric Evans’ original book. There’s also a great series about effective aggregate design written by Vaughn Vernon. Definitely worth reading.

It can be cumbersome to manually work with domain events. Thankfully, Spring Framework allows us to easily publish and handle domain events when working with aggregate roots using data repositories.

2. Maven Dependencies

Spring Data introduced @DomainEvents in Ingalls release train. It’s available for any kind of repository.

Code samples provided for this article use Spring Data JPA. The simplest way to integrate Spring domain events with our project is to use the Spring Boot Data JPA Starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

3. Publish Events Manually

First, let’s try to publish domain events manually. We’ll explain the @DomainEvents usage in the next section.

For the needs of this article, we’ll use an empty marker class for domain events – the DomainEvent.

We’re going to use standard ApplicationEventPublisher interface.

There’re two good places where we can publish events: service layer or directly inside the aggregate.

3.1. Service Layer

We can simply publish events after calling the repository save method inside a service method.

If a service method is part of a transaction and we handle the events inside the listener annotated with @TransactionalEventListener, then events will be handled only after the transaction commits successfully.

Therefore, there’s no risk of having “fake” events handled when the transaction is rolled back and the aggregate isn’t updated:

@Service
public class DomainService {
 
    // ...
    @Transactional
    public void serviceDomainOperation(long entityId) {
        repository.findById(entityId)
            .ifPresent(entity -> {
                entity.domainOperation();
                repository.save(entity);
                eventPublisher.publishEvent(new DomainEvent());
            });
    }
}

Here’s a test that proves events are indeed published by serviceDomainOperation:

@DisplayName("given existing aggregate,"
    + " when do domain operation on service,"
    + " then domain event is published")
@Test
void serviceEventsTest() {
    Aggregate existingDomainEntity = new Aggregate(1, eventPublisher);
    repository.save(existingDomainEntity);

    // when
    domainService.serviceDomainOperation(existingDomainEntity.getId());

    // then
    verify(eventHandler, times(1)).handleEvent(any(DomainEvent.class));
}

3.2. Aggregate

We can also publish events directly from within the aggregate.

This way we manage the creation of domain events inside the class which feels more natural for this:

@Entity
class Aggregate {
    // ...
    void domainOperation() {
        // some business logic
        if (eventPublisher != null) {
            eventPublisher.publishEvent(new DomainEvent());
        }
    }
}

Unfortunately, this might not work as expected because of how Spring Data initializes entities from repositories.

Here’s the corresponding test that shows the real behavior:

@DisplayName("given existing aggregate,"
    + " when do domain operation directly on aggregate,"
    + " then domain event is NOT published")
@Test
void aggregateEventsTest() {
    Aggregate existingDomainEntity = new Aggregate(0, eventPublisher);
    repository.save(existingDomainEntity);

    // when
    repository.findById(existingDomainEntity.getId())
      .get()
      .domainOperation();

    // then
    verifyNoInteractions(eventHandler);
}

As we can see, the event isn’t published at all. Having dependencies inside the aggregate might not be a great idea. In this example, ApplicationEventPublisher is not initialized automatically by Spring Data.

The aggregate is constructed by invoking the default constructor. To make it behave as we would expect, we’d need to manually recreate entities (e.g. using custom factories or aspect programming).

Also, we should avoid publishing events immediately after the aggregate method finishes. At least, unless we are 100% sure this method is part of a transaction. Otherwise, we might have “spurious” events published when change is not yet persisted. This might lead to inconsistencies in the system.

If we want to avoid this, we must remember to always call aggregate methods inside a transaction. Unfortunately, this way we couple our design heavily to the persistence technology. We need to remember that we don’t always work with transactional systems.

Therefore, it’s generally a better idea to let our aggregate simply manage a collection of domain events and return them when it’s about to get persisted.

In the next section, we’ll explain how we can make domain events publishing more manageable by using @DomainEvents and @AfterDomainEvents annotations.

4. Publish Events Using @DomainEvents

Since Spring Data Ingalls release train we can use the @DomainEvents annotation to automatically publish domain events.

A method annotated with @DomainEvents is automatically invoked by Spring Data whenever an entity is saved using the right repository.

Then, events returned by this method are published using the ApplicationEventPublisher interface:

@Entity
public class Aggregate2 {
 
    @Transient
    private final Collection<DomainEvent> domainEvents;
    // ...
    public void domainOperation() {
        // some domain operation
        domainEvents.add(new DomainEvent());
    }

    @DomainEvents
    public Collection<DomainEvent> events() {
        return domainEvents;
    }
}

Here’s the example explaining this behavior:

@DisplayName("given aggregate with @DomainEvents,"
    + " when do domain operation and save,"
    + " then event is published")
@Test
void domainEvents() {
 
    // given
    Aggregate2 aggregate = new Aggregate2();

    // when
    aggregate.domainOperation();
    repository.save(aggregate);

    // then
    verify(eventHandler, times(1)).handleEvent(any(DomainEvent.class));
}

After domain events are published, the method annotated with @AfterDomainEventsPublication is called.

The purpose of this method is usually to clear the list of all events, so they aren’t published again in the future:

@AfterDomainEventPublication
public void clearEvents() {
    domainEvents.clear();
}

Let’s add this method to the Aggregate2 class and see how it works:

@DisplayName("given aggregate with @AfterDomainEventPublication,"
    + " when do domain operation and save twice,"
    + " then an event is published only for the first time")
@Test
void afterDomainEvents() {
 
    // given
    Aggregate2 aggregate = new Aggregate2();

    // when
    aggregate.domainOperation();
    repository.save(aggregate);
    repository.save(aggregate);

    // then
    verify(eventHandler, times(1)).handleEvent(any(DomainEvent.class));
}

We clearly see that event is published only for the first time. If we removed the @AfterDomainEventPublication annotation from the clearEvents method, then the same event would be published for the second time.

However, it’s up to the implementor what would actually happen. Spring only guarantees to call this method – nothing more.

5. Use AbstractAggregateRoot Template

It’s possible to further simplify publishing of domain events thanks to the AbstractAggregateRoot template class. All we have to do is to call register method when we want to add the new domain event to the collection of events:

@Entity
public class Aggregate3 extends AbstractAggregateRoot<Aggregate3> {
    // ...
    public void domainOperation() {
        // some domain operation
        registerEvent(new DomainEvent());
    }
}

This is a counterpart to the example shown in the previous section.

Just to make sure everything works as expected – here are the tests:

@DisplayName("given aggregate extending AbstractAggregateRoot,"
    + " when do domain operation and save twice,"
    + " then an event is published only for the first time")
@Test
void afterDomainEvents() {
 
    // given
    Aggregate3 aggregate = new Aggregate3();

    // when
    aggregate.domainOperation();
    repository.save(aggregate);
    repository.save(aggregate);

    // then
    verify(eventHandler, times(1)).handleEvent(any(DomainEvent.class));
}

@DisplayName("given aggregate extending AbstractAggregateRoot,"
    + " when do domain operation and save,"
    + " then an event is published")
@Test
void domainEvents() {
    // given
    Aggregate3 aggregate = new Aggregate3();

    // when
    aggregate.domainOperation();
    repository.save(aggregate);

    // then
    verify(eventHandler, times(1)).handleEvent(any(DomainEvent.class));
}

As we can see, we can produce a lot less code and achieve exactly the same effect.

6. Implementation Caveats

While it might look like a great idea to use the @DomainEvents feature at first, there are some pitfalls we need to be aware of.

6.1. Unpublished Events

When working with JPA we don’t necessarily call save method when we want to persist the changes.

If our code is part of a transaction (e.g. annotated with @Transactional) and makes changes to the existing entity, then we usually simply let the transaction commit without explicitly calling the save method on a repository. So, even if our aggregate produced new domain events they will never get published.

We need also remember that @DomainEvents feature works only when using Spring Data repositories. This might be an important design factor.

6.2. Lost Events

If an exception occurs during events publication, the listeners will simply never get notified.

Even if we could somehow guarantee notification of event listeners, currently there’s no backpressure to let publishers know something went wrong. If event listener gets interrupted by an exception, the event will remain unconsumed and it will never be published again.

This design flaw is known to the Spring dev team. One of the lead developers even suggested a possible solution to this problem.

6.3. Local Context

Domain events are published using a simple ApplicationEventPublisher interface.

By default, when using ApplicationEventPublisher, events are published and consumed in the same thread. Everything happens in the same container.

Usually, we want to send events through some kind of message broker, so the other distributed clients/systems get notified. In such case, we’d need to manually forward events to the message broker.

It’s also possible to use Spring Integration or third-party solutions, such as Apache Camel.

7. Conclusion

In this article, we’ve learned how to manage aggregate domain events using @DomainEvents annotation.

This approach can greatly simplify events infrastructure so we can focus only on the domain logic. We just need to be aware that there’s no silver bullet and the way Spring handles domain events is not an exception.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

Partner – Orkes – NPI EA (cat = Spring)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Partner – Orkes – NPI EA (tag = Microservices)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)