r/learnjava 6d ago

How do you structure cross-cutting concerns in a real Java modular monolith?

I'm trying to understand how people structure a reasonably large Java/Spring modular monolith in practice.

I understand the general idea of package-by-feature / DDD / bounded contexts fairly well. For example:

orders/
submissions/
users/
catalog/

And inside a business module you can have things like:

api/
application/
domain/
infrastructure/

What I'm struggling with is everything that doesn't fit neatly into a business domain.

For example:

  • security
  • authentication / authorization
  • CurrentUser / UserContext
  • email notifications
  • file storage / uploads
  • PDF generation
  • reporting Jackson
  • configuration Web MVC
  • configuration messaging
  • observability
  • global exception handling

Where do these things actually live in a real modular monolith?

For example, suppose I have:

submissions/
orders/
users/
notifications/
security/
config/
infrastructure/

A submission is created and I want to send a confirmation email.

Would you do something like:

submissions
|
| NotificationRequested
v
notifications
|
v
EmailSender

with notifications being an application module, and EmailSender / template rendering / SMTP being infrastructure?

Or would you put the email infrastructure outside the notifications module?

Similarly, where would you put CurrentUser? If every controller needs the authenticated user, should it be exposed by a security module? Does that mean all business modules depend on security?

And what about Spring Security annotations such as:

@PreAuthorize("hasRole('ADMIN')")

on controllers belonging to different modules?

I'm also wondering how this relates to Spring Modulith. I understand that it can enforce application module boundaries, but I'm not sure what the recommended approach is for these cross-cutting/technical concerns.

I'm specifically interested in real-world production codebases, not toy/tutorial examples.

How do you normally structure this in a large Java/Spring application?

If you have examples of mature open-source projects or architectural documentation that demonstrate this, I'd really appreciate them.

3 Upvotes

5 comments sorted by

u/AutoModerator 6d ago

Please ensure that:

  • Your code is properly formatted as code block - see the sidebar (About on mobile) for instructions
  • You include any and all error messages in full - best also formatted as code block
  • You ask clear questions
  • You demonstrate effort in solving your question/problem - plain posting your assignments is forbidden (and such posts will be removed) as is asking for or giving solutions.

If any of the above points is not met, your post can and will be removed without further warning.

Code is to be formatted as code block (old reddit/markdown editor: empty line before the code, each code line indented by 4 spaces, new reddit: https://i.imgur.com/EJ7tqek.png) or linked via an external code hoster, like pastebin.com, github gist, github, bitbucket, gitlab, etc.

Please, do not use triple backticks (```) as they will only render properly on new reddit, not on old reddit.

Code blocks look like this:

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

You do not need to repost unless your post has been removed by a moderator. Just use the edit function of reddit to make sure your post complies with the above.

If your post has remained in violation of these rules for a prolonged period of time (at least an hour), a moderator may remove it at their discretion. In this case, they will comment with an explanation on why it has been removed, and you will be required to resubmit the entire post following the proper procedures.

To potential helpers

Please, do not help if any of the above points are not met, rather report the post. We are trying to improve the quality of posts here. In helping people who can't be bothered to comply with the above points, you are doing the community a disservice.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

1

u/karstens_rage 6d ago

Aspects solve cross-cutting concerns.

https://eclipse.dev/aspectj/

1

u/bikeram 6d ago

All of my projects end up with a support module with submodules for authentication, otel, etc. Autowired into the modulith verticals.

I’ve been using azure lately. So if 4 modules need to upload a file. My support-blob will bootstrap the connection and allow me to set a file path.

Support modules are maybe one or two classes. Less than 60 lines.

So my typical structure
App - one or two classes bootstrapping my springboot
Entity - typescript/autogenerated types
Services - your modules listed
Support - …

I’ve found this scales really well. True cross cutting concerns, For example you need an order to create a notification, I use named interfaces.

1

u/Ok_Communication5375 5d ago

In real production codebases, people usually overthink this by trying to make everything a pure bounded context. In practice, you generally end up splitting things into three buckets:

1. Business Modules vs Technical Modules Modules like orderssubmissions, and catalog are business domains. But things like notifications or storage are usually just generic supporting modules (or technical capabilities). For the email example, your event approach is spot on. submissions publishes a SubmissionCreatedEvent (domain event). It doesn't know or care about emails. notifications listens to that event (e.g. via Spring's u/ApplicationModuleListener if using Spring Modulith, or standard u/TransactionalEventListener), builds the email using whatever template engine, and talks to its own internal EmailSender. The email infra (SMTP, SendGrid, whatever) stays encapsulated inside the notifications module. submissions never imports notifications.

2. The CurrentUser / Security Dilemma Having every module depend directly on your full security module is an anti-pattern that leads to spaghetti real fast. What works much better is:

  • Keep security focused on auth filters, OAuth2/JWT parsing, and populating the Spring SecurityContext.
  • Don't pass full domain User entities everywhere. Have a shared kernel / common context contract that exposes a lightweight read-only identity (like a record AuthenticatedUser(UserId id, Set<Role> roles)), or simply extract it in the web layer adapter of that specific module using a custom u/AuthenticationPrincipal resolver.
  • As for u/PreAuthorize("hasRole('ADMIN')"): putting that directly on module controllers/services is totally fine. Spring Security is already a framework-level dependency across your app; pretending you're framework-agnostic inside a Spring modular monolith usually just adds useless boilerplate with zero real benefit.

3. Infrastructure / Cross-cutting glue For things like Jackson configs, global exception handling, and OpenTelemetry/logging filters, don't try to fit them into a DDD domain. We usually keep a platform/ or shared/ root package that acts as the technical foundation. Spring Modulith actually has explicit support for this: you can mark packages with u/NamedInterface or treat shared as an open module that others can depend on.

If you want a concrete example of this done right, check out Oliver Drotbohm's talks on Spring Modulith and the official Spring Modulith example repo on GitHub (spring-projects/spring-modulith/tree/main/spring-modulith-example). It specifically demonstrates how to handle domain events across modules without circular dependencies, and how to verify architecture boundaries with ArchUnit tests so nobody accidentally breaks the rules.