博客 Engineering

One In-App Notification, Many Notifiers: A Merged Fix for Halo

英文文章. Halo created a duplicate in-app notification for every enabled notifier. The merged fix still sends through each notifier but creates one in-app message.

Javahalo-dev/haloProject ReactornotificationsCMS

A reactive-pipeline fix in one of the most-starred projects in this series.

简短回答

In Halo, a subscriber with several notifiers enabled, such as email plus a plugin notifier, got one duplicate in-app notification per notifier. The fix collects the prepared notification elements, sends one through each notifier, and creates a single in-app notification. It was merged as PR #10264 and closed issue #7747.

The problem

Halo is an open-source website builder and CMS written in Java. Issue #7747, reported on Halo 2.21.7, showed duplicate notification messages once more than one notifier was installed.

Root cause

DefaultNotificationCenter.dispatchNotification() ran a reactive pipeline per notifier, and each branch both sent the notification and created an in-app Notification. With two notifiers, the user got two in-app messages for the same event.

The fix

.flatMap(descriptor -> prepareNotificationElement(subscriber, reason, descriptor))
.collectList()
.filter(elements -> !elements.isEmpty())
.flatMap(elements -> {
    var dispatchMono = Flux.fromIterable(elements)
            .flatMap(this::sendNotification)
            .then();
    if (subscriber.isAnonymous()) {
        return dispatchMono;
    }
    // create notification for user
    var innerNotificationMono = createNotification(elements.get(0));
    return Mono.when(dispatchMono, innerNotificationMono);
})

The change also fixes a variable name typo (innerNofificationMono). A new test sets up two notifiers and verifies two sends and one in-app notification:

verify(spyNotificationCenter, times(2)).sendNotification(any());
verify(spyNotificationCenter, times(1)).createNotification(any());

By the numbers

  • Repository: halo-dev/halo, 40,107 stars (Nov 3, 2026), Java, GPL-3.0
  • Files changed: 2 (1 source file, 1 test file)
  • Lines: +42 / -4
  • Commits: 1
  • Opened: Aug 21, 2026. Merged: Aug 25, 2026. Time to merge: 3 days, 20 hours
  • Review: approved and merged by a maintainer; CLA signed; quality gate passed

How the review went

The PR included a release note: "Fix duplicate in-app notifications when multiple notifiers are enabled." Automated checks reported the quality gate passed and 87.5% patch coverage. A maintainer approved and merged it without requesting changes.

常见问题解答

Do external notifiers still each receive the message?

Yes. Every enabled notifier still gets a send. Only the in-app notification is deduplicated.

Where can I read more?

See the pull request and the original issue.

目标

Reactive pipelines make it easy to do one thing per element when you meant to do it once. Collecting first, then splitting the side effects, states the intent in the code.

运行闭环

把生产信号转化为已审查的修复。

开始免费试用,了解 Prilog 如何把真实事故映射到代码级 Pull Request。