Backend Development 10 min read

Why Spring Skips Static Field Injection and How It Works Internally

This article explains Spring's bean creation process, focusing on the populateBean method and the AutowiredAnnotationBeanPostProcessor, and shows why static fields are ignored during field injection while also offering a way to inject them via a non‑static @Autowired method.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Why Spring Skips Static Field Injection and How It Works Internally

When Spring creates a bean, it not only instantiates the bean but also populates its properties. The overall flow is roughly:

Reflection creates the bean – createBeanInstance

Populate the bean – populateBean

Initialize the bean (including AOP) – initializeBean

Register the bean's destroy method – registerDisposableBeanIfNecessary

The property‑population logic lives inside populateBean :

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    // ...
    PropertyDescriptor[] filteredPds = null;
    if (hasInstAwareBpps) {
        if (pvs == null) {
            pvs = mbd.getPropertyValues();
        }
        for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
            // here
            PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
            if (pvsToUse == null) {
                if (filteredPds == null) {
                    filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
                }
                pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                if (pvsToUse == null) {
                    return;
                }
            }
            pvs = pvsToUse;
        }
    }
    // ...
    if (pvs != null) {
        applyPropertyValues(beanName, mbd, bw, pvs);
    }
}

Besides applyPropertyValues , most of the field‑injection logic is handled by InstantiationAwareBeanPostProcessor , especially its postProcessProperties method, which processes annotations such as @Autowired and @Resource .

InstantiationAwareBeanPostProcessor is an interface; its two important implementations are AutowiredAnnotationBeanPostProcessor (handles @Autowired ) and CommonAnnotationBeanPostProcessor (handles @Resource ).

The core of AutowiredAnnotationBeanPostProcessor is its postProcessProperties method:

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // Find metadata for fields/methods annotated with @Autowired
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        // Perform injection
        metadata.inject(bean, beanName, pvs);
    } catch (BeanCreationException ex) {
        throw ex;
    } catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    return pvs;
}

Finding the metadata is done by findAutowiringMetadata :

private InjectionMetadata findAutowiringMetadata(String beanName, Class
clazz, @Nullable PropertyValues pvs) {
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                metadata = buildAutowiringMetadata(clazz);
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    return metadata;
}

The helper needsRefresh simply returns true when the cached metadata is null or stale:

public static boolean needsRefresh(@Nullable InjectionMetadata metadata, Class
clazz) {
    return (metadata == null || metadata.needsRefresh(clazz));
}

If a refresh is needed, buildAutowiringMetadata scans the class hierarchy for fields and methods annotated with injection annotations:

private InjectionMetadata buildAutowiringMetadata(final Class
clazz) {
    if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
        return InjectionMetadata.EMPTY;
    }
    List
elements = new ArrayList<>();
    Class
targetClass = clazz;
    do {
        List
currElements = new ArrayList<>();
        // Process fields
        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            MergedAnnotation
ann = findAutowiredAnnotation(field);
            if (ann != null) {
                if (Modifier.isStatic(field.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static fields: " + field);
                    }
                    return;
                }
                boolean required = determineRequiredStatus(ann);
                currElements.add(new AutowiredFieldElement(field, required));
            }
        });
        // Process methods (omitted for brevity)
        elements.addAll(0, currElements);
        targetClass = targetClass.getSuperclass();
    } while (targetClass != null && targetClass != Object.class);
    return InjectionMetadata.forElements(elements, clazz);
}

Field processing relies on ReflectionUtils.doWithLocalFields which iterates over all declared fields and applies the supplied FieldCallback (the lambda shown above). The callback looks for @Autowired (or other supported annotations) via findAutowiredAnnotation :

@Nullable
private MergedAnnotation
findAutowiredAnnotation(AccessibleObject ao) {
    MergedAnnotations annotations = MergedAnnotations.from(ao);
    for (Class
type : this.autowiredAnnotationTypes) {
        MergedAnnotation
annotation = annotations.get(type);
        if (annotation.isPresent()) {
            return annotation;
        }
    }
    return null;
}

If a field is static, the processor logs a message and skips injection, which is why Spring never injects static fields. The actual injection is performed by InjectionMetadata.inject :

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    Collection
elementsToIterate = (this.checkedElements != null ? this.checkedElements : this.injectedElements);
    if (!elementsToIterate.isEmpty()) {
        for (InjectedElement element : elementsToIterate) {
            element.inject(target, beanName, pvs);
        }
    }
}

For static fields, Spring deliberately ignores them because static members belong to the class rather than a bean instance, which conflicts with Spring's design of managing object lifecycles. If you really need to set a static field, you can do it inside a non‑static @Autowired method that assigns a value to the static variable.

However, it is generally discouraged to inject static fields, as it breaks the intended bean‑scoped management and can lead to unexpected side effects across the entire application.

backendjavaSpringdependency injectionAutowiredBeanPostProcessorStatic Field
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.