spring-projects/spring-framework · error · UnsupportedOperationException

An advice method can never be a constructor

Error message

An advice method can never be a constructor

What it means

Thrown by AspectJAdviceParameterNameDiscoverer.getParameterNames(Constructor) (line 288-291) when raiseExceptions is true and the discoverer is asked for the parameter names of a Constructor rather than a Method. Advice in Spring is always a Method, never a Constructor; the discoverer is only meaningful for methods, so it refuses the constructor call (returning null normally, throwing when raiseExceptions).

Solutions

  1. Do not use AspectJAdviceParameterNameDiscoverer for constructor parameter discovery — use a plain DefaultParameterNameDiscoverer or StandardReflectionParameterNameDiscoverer.
  2. Keep raiseExceptions=false (default) if the discoverer participates in a general chain.
  3. Separate the discoverer instance used for advice binding from the one used for general reflection.

Example fix

// before — advice discoverer (raiseExceptions=true) reused for constructors
DefaultParameterNameDiscoverer d = new DefaultParameterNameDiscoverer();
AspectJAdviceParameterNameDiscoverer aj = new AspectJAdviceParameterNameDiscoverer(expr);
aj.setRaiseExceptions(true); d.addDiscoverer(aj);
String[] names = d.getParameterNames(MyClass.class.getConstructors()[0]); // throws

// after — separate discoverer for constructor reflection
DefaultParameterNameDiscoverer ctorDiscoverer = new DefaultParameterNameDiscoverer();
String[] names = ctorDiscoverer.getParameterNames(MyClass.class.getConstructors()[0]);
Defensive patterns

Strategy: validation

Validate before calling

import java.lang.reflect.Constructor;
import org.springframework.aop.aspectj.AspectJAdviceParameterNameDiscoverer;

// Do not route constructor queries through an advice discoverer with raiseExceptions=true.
String[] safeGetCtorParamNames(Constructor<?> ctor,
        AspectJAdviceParameterNameDiscoverer adviceDiscoverer) {
    if (adviceDiscoverer != null) {
        // use a separate plain discoverer for constructors
        return new org.springframework.core.DefaultParameterNameDiscoverer().getParameterNames(ctor);
    }
    return adviceDiscoverer.getParameterNames(ctor);
}

Type guard

boolean isMethodDiscoveryTarget(Class<?> member) {
    return member == java.lang.reflect.Method.class;
}

Try / catch

try {
    String[] names = discoverer.getParameterNames(constructor);
} catch (UnsupportedOperationException ex) {
    if (ex.getMessage().contains("advice method can never be a constructor")) {
        // switch to a plain DefaultParameterNameDiscoverer for constructors
    } else { throw ex; }
}

Prevention

When it happens

Trigger: A ParameterNameDiscoverer chain that includes AspectJAdviceParameterNameDiscoverer is invoked with a Constructor — e.g. DefaultParameterNameDiscoverer.getParameterNames(ctor) where the advice discoverer is in the chain and raiseExceptions was set to true. This is almost always a misconfiguration or misuse rather than a real aspect scenario.

Common situations: Reusing an AspectJAdviceParameterNameDiscoverer instance (or a DefaultParameterNameDiscoverer that has one added) for general constructor parameter-name discovery, while raiseExceptions=true. Should not occur in normal aspect processing.

Related errors


AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09). Data as JSON: /api/errors/c70309fe063963dd. Report an issue: GitHub.

Appendix: source

Thrown at spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java:290

						this.numberOfRemainingUnboundArguments + " argument(s) could not be bound");
			}
			else {
				// convention for failing is to return null, allowing participation in a chain of responsibility
				return null;
			}
		}
	}

	/**
	 * An advice method can never be a constructor in Spring.
	 * @return {@code null}
	 * @throws UnsupportedOperationException if
	 * {@link #setRaiseExceptions(boolean) raiseExceptions} has been set to {@code true}
	 */
	@Override
	public String @Nullable [] getParameterNames(Constructor<?> ctor) {
		if (this.raiseExceptions) {
			throw new UnsupportedOperationException("An advice method can never be a constructor");
		}
		else {
			// we return null rather than throw an exception so that we behave well
			// in a chain-of-responsibility.
			return null;
		}
	}


	private void bindParameterName(int index, @Nullable String name) {
		this.parameterNameBindings[index] = name;
		this.numberOfRemainingUnboundArguments--;
	}

	/**
	 * If the first parameter is of type JoinPoint or ProceedingJoinPoint, bind "thisJoinPoint" as
	 * parameter name and return true, else return false.
	 */

View on GitHub (pinned to 69bf83ad71)