Pointcut Expressions
Pointcut Expressions
Knowing how to write an advice method is only half the story. The other half — and the part that separates maintainable aspects from fragile ones — is telling Spring exactly which join points that advice should intercept. That is the job of a pointcut expression. Master the expression language and you gain surgical control over your cross-cutting concerns without touching a single line of business code.
What a Pointcut Expression Actually Is
A pointcut expression is a string written in Spring AOP's expression language (borrowed from AspectJ). It is evaluated at context startup: Spring inspects every bean, resolves the expression against each method, and builds a proxy for any bean whose methods match. At runtime the proxy intercepts only the matching calls — everything else passes through without overhead.
You attach an expression either directly on an advice annotation or on a dedicated @Pointcut method, then reference that method by name:
@Pointcut methods. If you paste the same expression string into five advice annotations, a one-character rename of a package breaks all five silently at startup — but only the one pointcut you forgot to update.
The execution() Designator
execution() is the workhorse of Spring AOP. It matches method execution join points based on a method signature pattern. The full grammar is:
Square brackets mark optional parts. In practice you will use three or four segments. Here is a progressive set of real examples, each more selective than the last:
Pattern tokens you need to know:
*— matches any single segment (one package level, one type name, one method name, one return type). Does not span dots...— in the package position matches any number of sub-packages; in the parameter position matches any number and type of parameters.()— no arguments exactly.(*)— exactly one argument of any type.(..)— zero or more arguments of any type (most common).
execution(* *(..)) matches every method on every proxied bean, including infrastructure beans you did not intend to intercept. Start with the fully-qualified package and widen only as needed.
The within() Designator
within() restricts matching to join points within a type (class or set of classes). It does not care about method signatures — only where the method lives. This makes it the right choice when you want to intercept all activity inside a layer or a specific class, regardless of method names or return types:
The last form — matching on a type-level annotation — is particularly powerful. Every class you annotate with @Service is automatically covered without listing package names. If you later add a service in a new package, the pointcut picks it up with zero changes.
execution() vs within() — Choosing the Right Tool
These two designators often overlap, but they are not interchangeable:
- Use
execution()when the signature is what matters — a specific return type, a naming convention likefind*orsave*, or a particular parameter list. - Use
within()when the location is what matters — "intercept everything in the service layer" or "intercept everything in this specific class". - Use both combined when you need both constraints: a method name pattern within a specific package.
&& (AND), || (OR), and ! (NOT) to compose expressions. In XML configuration you must write and, or, not because XML escapes the ampersand — but in annotations the Java string && works directly.
Annotation-Based Pointcuts with @annotation()
A common real-world pattern is to define a custom annotation and then write a pointcut that matches any method carrying that annotation. This gives library authors and framework designers a clean opt-in hook:
This pattern is far more maintainable than package-based expressions when you need fine-grained control because the annotation travels with the method even if it moves packages.
Common Mistakes and How to Avoid Them
new-constructed object will never fire, because there is no proxy. Always inject your beans through Spring's IoC container.
- Self-invocation is not intercepted. If
OrderService.methodA()callsthis.methodB()internally, the AOP proxy is bypassed formethodB. The fix is to inject the bean into itself (Spring 6 handles the circular dependency) or refactor into a separate bean. - Private and package-private methods are never matched by Spring AOP (it relies on JDK dynamic proxies or CGLIB, both of which only override public/protected methods). If your pointcut seems to not fire, check method visibility first.
- The
..in a package pattern matches zero or more segments, socom.example..matchescom.exampleitself and any nested packages. This is a common source of accidentally broad matches.
Validating Your Expressions
Rather than guessing whether a pattern matches, write a quick integration test and log join points:
Gate it behind a Spring Profile so it never reaches production. Run the app, exercise your features, and examine the console output. If a method you expected to match does not appear — check visibility, bean management, and self-invocation.
Summary
execution() gives you signature-level precision: match on return type, method name, and parameter types. within() gives you location-level precision: match everything inside a type or package. Combine them with &&, ||, and !, and use @annotation() for opt-in interception. Name shared expressions with @Pointcut methods for reusability and keep expressions as specific as your use case warrants. In the next lesson you will apply these tools to the most flexible advice type: @Around.