SpringBoot CGLIB Proxy Returns Null for Initialized Fields – Causes and Fixes
This article explains why a SpringBoot service field initialized with a value becomes null when accessed through a CGLIB proxy, analyzes the role of final methods and Objenesis in the proxy creation process, and provides three practical solutions to prevent the null value.
1. Problem Reproduction
The issue occurs when a class defines an instance variable with an initial value, but after being proxied by Spring AOP, a NullPointerException (NPE) appears.
<code>@Service
public class PersonService {
private String name = "Pack";
public final void save() {
System.err.printf("class: %s, name: %s%n", this.getClass(), this.name);
}
}</code>The save method is marked final and prints the class object and the name field.
2. Aspect Definition
<code>@Component
@Aspect
public class PersonAspect {
@Pointcut("execution(* com.pack.aop.PersonService.*(..))")
private void log() {}
@Around("log()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("before...");
Object ret = pjp.proceed();
System.out.println("after...");
return ret;
}
}</code>The aspect simply logs before and after the target method.
3. Spring Proxy Mechanism
Spring AOP creates proxies using JDK dynamic proxies or CGLIB. When the target does not implement any interface, CGLIB is used (the default in Spring Boot).
Final methods cannot be advised because CGLIB cannot override them in the generated subclass.
4. Observed Result
<code>@Service
public class AppRunService {
private final PersonService personService;
public AppRunService(PersonService personService) {
this.personService = personService;
}
@PostConstruct
public void init() {
this.personService.save();
}
}</code>Running the application prints:
<code>class: class com.pack.aop.PersonService$$EnhancerBySpringCGLIB$$557ca555, name: null</code>Although name was initialized to "Pack", it appears as null .
5. Root Cause Analysis
Spring creates a CGLIB proxy; final methods cannot be overridden, so the call goes to the original method in the superclass.
The proxy instance is created via Objenesis , which instantiates the class without invoking its constructor. Since the field initialization occurs in the constructor, the field remains null .
The relevant code in Spring’s proxy creation:
<code>class ObjensisisCglibAopProxy extends CglibAopProxy {
private static final SpringObjenesis objenesis = new SpringObjenesis();
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
Class<?> proxyClass = enhancer.createClass();
Object proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
((Factory) proxyInstance).setCallbacks(callbacks);
return proxyInstance;
}
}</code>Objenesis creates the object while skipping the constructor, leaving the field uninitialized.
6. Solutions
Three ways to fix the problem:
6.1 Add final to the field
<code>public class PersonService {
private final String name = "Pack";
}</code>Result:
<code>class: class com.pack.aop.PersonService$$EnhancerBySpringCGLIB$$87211922, name: Pack</code>The value is correctly printed because the final field is inlined at compile time.
6.2 Remove final from the method
Allowing CGLIB to override save lets the proxy invoke the method after the advice, and the field retains its initialized value.
6.3 Set system property to disable Objensis
<code>-Dspring.objenesis.ignore=true</code>Spring will then use the regular constructor path, preserving field initialization.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.