- Chapter
- Chapter 1 — Cross-Cutting Concerns
- Prerequisites
- Book 4 Volume I (container, beans, post-processors)
- You will be able to
- Identify cross-cutting concerns, explain how Spring implements each one, and decide where a new concern should be applied
- Covers
- Logging · Security · Transactions · Auditing · Metrics · Caching
On this page
- 1. Concept / Definition
- 2. Why It Exists
- 3. Core Concepts
- 4. How It Works Internally
- 4A. Deep Dive — Choosing the Interception Layer
- 5. APIs / Features / Syntax
- 6. Practical Examples
- 7. Common Problems & Mistakes
- 8. Performance & Production Considerations
- 9. Architecture & Design Considerations
- 10. Interview Questions & Answers
1Concept / Definition
A concern is a particular area of interest or responsibility in a system. A core concern is the reason a module exists — placing an order, charging a payment. A cross-cutting concern is a responsibility that must be applied uniformly across many modules but is not the purpose of any of them.
| Property | Core concern | Cross-cutting concern |
|---|---|---|
| Belongs to | One module (Orders) | Many modules (every service) |
| Changes when | Business rules change | Operational or regulatory policy changes |
| Expressed as | Domain classes and methods | Policies: "every write is audited", "every public API is timed" |
| Owner | Product/feature team | Platform, security, operations |

2Why It Exists
Enterprise applications must be observable, secure, consistent and fast — requirements that apply to every feature. In the EJB era the container provided some of them (transactions, security) through deployment descriptors, but only for EJB components and only for a fixed set of concerns. Spring generalised the idea: any bean can receive any concern through interception, and new concerns can be added by applications and libraries. Understanding which concerns are cross-cutting is the first step to applying them consistently.
3Core Concepts

3.1 Logging
Entry/exit logs, parameter and error logging, correlation and trace IDs. The policy ("log failures of every service call with the trace ID") is uniform; hand-written log statements in every method are not.
| Spring mechanism | Use |
|---|---|
Micrometer Observation / @Observed | Timing + tracing + logging correlation per operation |
| MDC via tracing integration | Trace/span IDs in every log line automatically |
Custom @Around aspect | Special entry/exit logging where genuinely needed |
3.2 Security
Authentication (who are you?) and authorisation (may you do this?). Web requests pass through the Spring Security filter chain; method-level rules are applied by a method-security interceptor.
@Service
class PayoutService {
@PreAuthorize("hasAuthority('payouts:approve') and #p.amount < 100000")
public void approve(Payout p) { ... } // no security code inside the method
}3.3 Transactions
Begin before, commit after, roll back on failure — around every business operation that touches transactional resources. @Transactional is applied by TransactionInterceptor (Chapter 5).
3.4 Auditing
Recording who changed what and when, often for regulatory reasons. Spring Data provides entity-level auditing (@CreatedBy, @LastModifiedDate with @EnableJpaAuditing); business-level audit trails ("payout approved by X") are commonly a custom aspect or domain events.
3.5 Metrics
Counters, timers and gauges for throughput, latency and errors. Micrometer's @Timed/@Counted and the Observation API record them through an aspect; Spring MVC, WebFlux, data sources and clients are instrumented automatically by Boot.
3.6 Caching
"If the result for these arguments is cached, return it; otherwise compute and store it." @Cacheable, @CachePut and @CacheEvict are applied by CacheInterceptor against a CacheManager (Caffeine, Redis, …).
@Cacheable(cacheNames = "fx-rates", key = "#from + '-' + #to")
public BigDecimal rate(String from, String to) { return fxClient.fetch(from, to); }
4How It Works Internally
Almost every concern above follows the same pattern: an annotation or configuration marks join points, an advisor (pointcut + interceptor) matches them, and a proxy created by a BeanPostProcessor runs the interceptor around the call.
| Concern | Marker | Interceptor | Enabled by (Boot auto-configures) |
|---|---|---|---|
| Transactions | @Transactional | TransactionInterceptor | @EnableTransactionManagement |
| Caching | @Cacheable etc. | CacheInterceptor | @EnableCaching |
| Method security | @PreAuthorize etc. | AuthorizationManagerBeforeMethodInterceptor | @EnableMethodSecurity |
| Async | @Async | AnnotationAsyncExecutionInterceptor | @EnableAsync |
| Metrics/tracing | @Timed, @Observed | TimedAspect, ObservedAspect | Aspect beans + AOP |
| Retry (Framework 7) | @Retryable | Retry interceptor | @EnableResilientMethods |
Web-layer concerns (authentication, CORS, request logging) are usually applied earlier by servlet filters or WebFlux WebFilters — also interception, but at the HTTP boundary rather than on beans.
4ADeep Dive — Choosing the Interception Layer
A concern can be applied at several layers. Choosing the right one avoids duplicate processing and gaps.
| Layer | Mechanism | Sees | Good for |
|---|---|---|---|
| HTTP boundary | Servlet Filter, WebFilter, HandlerInterceptor | Request/response, headers, user | Authentication, CORS, request logging, rate limiting |
| Service methods | AOP proxies (@Transactional, @PreAuthorize, aspects) | Method, arguments, return value | Transactions, authorisation rules on domain operations, caching, metrics per use case |
| Data access | Repository proxies, Hibernate interceptors, entity listeners | Entities, SQL | Entity auditing, soft-delete filters, exception translation |
| Messaging | Listener container interceptors, ChannelInterceptor | Messages | Tracing propagation, deduplication |
| Infrastructure | Micrometer instrumentation, JDBC proxies | Connections, calls | Low-level metrics, slow-query logs |
// Service layer: one aspect applies an audit policy to all annotated operations
@Target(METHOD) @Retention(RUNTIME)
public @interface Audited { String action(); }
@Aspect @Component
class AuditAspect {
private final AuditTrail trail;
AuditAspect(AuditTrail trail) { this.trail = trail; }
@AfterReturning("@annotation(audited)")
void record(JoinPoint jp, Audited audited) {
trail.record(audited.action(), currentUser(), jp.getArgs());
}
}5APIs / Features / Syntax
| Concern | Key annotations / APIs |
|---|---|
| Logging/tracing | @Observed, ObservationRegistry, MDC, Logback/Log4j2 patterns |
| Security | @PreAuthorize, @PostAuthorize, @Secured, SecurityFilterChain |
| Transactions | @Transactional, TransactionTemplate, PlatformTransactionManager |
| Auditing | @CreatedBy, @LastModifiedBy, @CreatedDate, AuditorAware, custom aspects |
| Metrics | @Timed, @Counted, MeterRegistry, Actuator /metrics |
| Caching | @Cacheable, @CachePut, @CacheEvict, @Caching, CacheManager |
6Practical Examples
6.1 One method, five concerns, zero infrastructure code
@Service
class TransferService {
@Transactional
@PreAuthorize("hasAuthority('transfers:write')")
@Observed(name = "transfer.execute")
@Audited(action = "TRANSFER")
@CacheEvict(cacheNames = "balances", key = "#cmd.from")
public TransferId execute(TransferCommand cmd) {
return ledger.move(cmd.from(), cmd.to(), cmd.amount()); // pure business logic
}
}6.2 Checking which concerns were applied
Advised advised = (Advised) ctx.getBean(TransferService.class); // it is a proxy
for (Advisor a : advised.getAdvisors()) {
log.info("advisor: {}", a.getAdvice().getClass().getSimpleName());
}
// TransactionInterceptor, AuthorizationManagerBeforeMethodInterceptor, ...7Common Problems & Mistakes
- Treating a cross-cutting concern as business logic. Scattered audit code drifts; declare policy once.
- Treating business logic as cross-cutting. Discount rules implemented as an "aspect" hide essential behaviour.
- Applying authorisation only at the web layer. Non-HTTP entry points bypass it.
- Double application. The same metric recorded by auto-instrumentation and a custom aspect.
- Forgetting that interception needs proxies. Self-invocation and private methods skip concerns (Chapter 3).
8Performance & Production Considerations
| Concern | Cost profile | Guidance |
|---|---|---|
| Logging | I/O-bound | Structured, sampled, async appenders; avoid logging large arguments |
| Security | Expression evaluation per call | Keep SpEL simple; cache authorisation data |
| Transactions | Connection hold time | Keep transactions short (Chapter 5) |
| Metrics | Low; tag cardinality matters | Avoid user IDs or URLs with IDs as tags |
| Caching | Memory, staleness | Bounded caches, TTLs, eviction on writes |
| Auditing | Extra writes | Write in the same transaction or via outbox |
9Architecture & Design Considerations
- Concerns are platform capabilities. Deliver them as starters and conventions so every service applies them identically.
- Declarative over programmatic. Annotations or configuration express policy; code expresses business.
- Visible policies. Keep a catalogue of the organisation's cross-cutting policies and where each is enforced.
- Concerns across services — tracing propagation, authentication tokens, idempotency — need the same uniformity at the network level (service mesh, gateways, shared libraries).
10Interview Questions & Answers
What is a cross-cutting concern?
A cross-cutting concern is behaviour that many parts of a system need, but that is not the main purpose of any of them. Logging, security checks, transaction management, auditing, metrics, tracing and caching are typical examples. If each class implements them by hand, the same code is duplicated across the system and mixed into business methods, which makes both harder to change. For example, changing the audit format would mean editing hundreds of methods. Frameworks such as Spring let you declare these concerns once and apply them consistently, so business code stays focused on business rules and policies can change in one place.
Answer: Spring offers annotations for most common cross-cutting concerns. @Transactional manages database transactions around a method. @PreAuthorize and @PostAuthorize enforce method-level security rules. @Cacheable, @CachePut and @CacheEvict add caching. @Timed and @Observed, from Micrometer, record metrics and traces. @Async runs a method on another thread, and @Retryable retries failed calls. @Validated triggers bean validation of method arguments. Each annotation declares the policy where it applies, while the implementation lives in infrastructure code shared by the whole application. For example, a payment method can be transactional, secured and timed with three annotations and no extra code.How does Spring apply these annotations at runtime?
At start-up, Spring's bean post-processors inspect each bean. If a bean has methods annotated with, for example, @Transactional or @Cacheable, Spring wraps it in a proxy, either a JDK dynamic proxy for interfaces or a CGLIB subclass, and injects that proxy wherever the bean is used. The proxy holds a chain of interceptors, also called advice. When a caller invokes a method through the proxy, the interceptors run in order, starting a transaction or checking a cache, then call the real method, and finally commit, roll back or store the result. Calls from inside the same object bypass the proxy.
Is authentication a cross-cutting concern at the service layer?
Authentication, establishing who the caller is, is normally handled once at the boundary, for example by Spring Security's filters for HTTP requests or by the messaging infrastructure for messages, so it is not a service-layer concern. Authorisation, deciding whether that caller may perform a specific operation, is a cross-cutting concern that should also be enforced at the service layer. Services can be invoked from several entry points, such as REST controllers, message listeners, scheduled jobs and batch processes, and only some pass through web security. Annotations such as @PreAuthorize on service methods protect business operations whatever the entry point.
How do you decide whether something should be an aspect?
Ask two questions. First, is it a uniform policy that should apply the same way to many methods, such as starting a transaction, recording a metric or writing an audit entry? Second, is it independent of the business purpose of those methods? If both answers are yes, an aspect or annotation-driven interceptor fits well. If the behaviour contains business rules, varies by use case, or needs detailed knowledge of the method's data, it belongs in domain or application code, where it is visible and testable. A useful warning sign is an aspect that inspects arguments to decide what to do; that is usually business logic hiding in infrastructure.
What layers can a concern be applied at in a Spring application?
A concern can be applied at several layers, and the right one depends on what information it needs. At the HTTP boundary, servlet filters and handler interceptors see requests and responses, which suits authentication, correlation IDs and request logging. At the service layer, AOP proxies see business method calls and arguments, which suits transactions, method security and auditing of business actions. At the data-access layer, repository interceptors and JPA entity listeners suit auditing of changes. Messaging interceptors handle the same needs for message flows, and infrastructure instrumentation such as Micrometer and OpenTelemetry covers metrics and tracing across all of them.
How would you ensure consistent auditing across fifty services?
Make auditing a platform capability rather than something each team builds. Publish an internal Spring Boot starter that provides a standard @Audited annotation or domain-event mechanism, a shared audit event schema with fields such as actor, action, target, outcome and correlation ID, and a reliable delivery path, typically a transactional outbox feeding a central audit store. Teams add the starter and annotate business actions; the starter handles formatting, delivery and retries. Enforce adoption with architecture tests and pipeline checks, review audit coverage during design reviews, and version the schema carefully, because auditors and compliance tools will depend on it for years.
How do cross-cutting concerns influence the platform strategy?
Cross-cutting concerns are where consistency matters most, so they should be delivered as platform capabilities rather than reimplemented by every team. Internal Spring Boot starters can provide security, logging, metrics, tracing and auditing defaults; API gateways can handle authentication, rate limiting and TLS at the edge; a service mesh can provide mutual TLS and retries between services; and a shared observability stack collects the results. This reduces each team's effort, makes behaviour consistent across services and lets policies, such as a new logging format or security rule, change centrally. Teams still own business concerns, while the platform owns the cross-cutting ones.