There appears to be a lot of confusion in the community on how to expression bean configuration or rather how to convert bean configuration from XML to Java (or Groovy, or Kotlin, or…) as it appears as if the different ways of configuration are completely different things.

But in fact they are more or less the same it is configuration expressed in a format (Java, XML, Properties) to instruct the Spring ApplicationContext (well actually the BeanFactory) to create beans based on those definitions (aka BeanDefinition). A BeanDefinition is the recipe on how to prepare/create a bean.

Lets create a small sample using a few classes and lets take a look under the hood on what is happening with those class.

public record Person(String name) {}

An interface for a service to greet the Person.

public interface Greeter {

  void greet(Person person);
}

The implementation that prints a greeting to the standard out.

public class SystemOutGreeter implements Greeter {

  public void greet(Person person) {
    System.out.printf("Greetings %s!%n", person.name());
  }
}

Using Property files for configuration

Now lets step into a time machine and go back 15 years to the inception of Spring. A day and age before XML and annotations but with the existence of the good old properties file. Create an application-context.properties and lets use that to bootstrap our ApplicationContext. Yes still today with Spring 6 we can use property files to create an ApplicationContext.

NOTE: As of Spring Framework 5.3 this feature has been marked as deprecated and might (and probably will be) removed in some future version of Spring.

person.(class)=biz.deinum.blog.context.springcontexts.Person
person.$0=Marten Deinum

greeter.(class)=biz.deinum.blog.context.springcontexts.SystemOutGreeter

Now that we have all the moving parts, for now, lets create an ApplicationContext with it.

public class SpringContextsApplication {

  public static void main(String[] args) throws Exception {
    try (var contextFromProperties =new GenericApplicationContext()) {

      var reader = new PropertiesBeanDefinitionReader(contextFromProperties);
      reader.loadBeanDefinitions("classpath:application-context.properties");
      contextFromProperties.refresh();

      doGreeting(contextFromProperties);
  }   
}

  private static void doGreeting(ApplicationContext ctx) {
    var greeter = ctx.getBean(Greeter.class);
    var person = ctx.getBean(Person.class);
    greeter.greet(person);
  }
}

So what does all of this do. First we need an ApplicationContext and for this we use the GenericApplicationContext class. Then we need something to load our BeanDefinitions with, which is where the BeanDefinitionReader comes into play. As we want to use a property file specifically the PropertiesBeanDefinitionReader. The PropertiesBeanDefinitionReader takes a property file and converts that into BeanDefinition instances. In our case it will create 2 BeanDefinition instances 1 for the Person and another for the SystemOutGreeter. The special .(class) notation specifies what the type of the bean is going to be (there are more see the javadoc for that). There is also person.$0=Marten Deinum this will set the first constructor argument of the Person to the specified value. It will use the data binding support in Spring for that which ultimately comes done to using reflection.

The loaded BeanDefinitions are added to the BeanFactory (the ApplicationContext is a BeanFactory on steroids) so that it can create the actual bean instances.

Before we can use an ApplicationContext we have to call the refresh() method on it. NOTE: in most cases this is done automatically!

After that we get the Greeter and Person from the ApplicationContext and print the greeting.

Output: Greetings, Marten Deinum!

What ultimately happens to create the person and greeter beans (through reflection as mentioned earlier) is the following:

var person = new Person("Marten Deinum");

and

var greeter = new SystemOutGreeter();

Using XML for configuration

Now lets do the same with XML. Create an applicationContext.xml to configure a Person and SystemOutGreeter.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="greeter" class="biz.deinum.blog.context.springcontexts.SystemOutGreeter" />

  <bean id="person" class="biz.deinum.blog.context.springcontexts.Person">
    <constructor-arg name="name" value="Marten Deinum" />
  </bean>
</beans>

The XML is more verbose then the property file. We have a beanelement which in turn has a class attribute to specify the type of the bean. Inside the bean element we can specify property or constructor-arg elements to specify which properties/constructor arguments to set, in our case we are setting the constructor argument name again to the given value.

To load this file we will need a BeanDefinitionReader which can load XML files, thus the XmlBeanDefinitionReader.

public class SpringContextsApplication {

  public static void main(String[] args) throws Exception {
    try (var contextFromXml = new GenericApplicationContext()) {}

      var reader = new XmlBeanDefinitionReader(contextFromXml);
      reader.loadBeanDefinitions("classpath:applicationContext.xml");
      contextFromXml.refresh();

      doGreeting(contextFromXml);
    }
  }

  private static void doGreeting(ApplicationContext ctx) {
    var greeter = ctx.getBean(Greeter.class);
    var person = ctx.getBean(Person.class);
    greeter.greet(person);
  }
}

The code is almost the same as the one with the property file, except for the highlighted lines which instead of loading property file now use a XML file for the configuration. The output is still the same.

The creation of the person and greeter is also still the same and again boils down to:

var person = new Person("Marten Deinum");

and

var greeter = new SystemOutGreeter();

However generally you will not be writing this code but instead you would be using the ClassPathXmlApplicationContext to do all this work for you.

public class SpringContextsApplication {

  public static void main(String[] args) throws Exception {
    try (var contextFromXml = new ClassPathXmlApplicationContext("applicationContext.xml")) {}
      doGreeting(contextFromXml);
  }    
 }
  ... // Method omitted
}

This code and the code above are basically identical in what happens internally, however this is a lot shorter.

Using Java for Configuration

We could also use the Java language to express the configuration. In Spring a Java configuration class is a regular class annotated with @Configuration and containing methods annotated with @Bean.

@Configuration
public class ApplicationContext {

  @Bean
  public Person person() {
    return new Person("Marten Deinum");
  }

  @Bean
  public Greeter greeter() {
    return new SystemOutGreeter();
  }
}

NOTE: The following code will NOT compile as it is using inaccessible classes from Spring it is merely here to show how it internally works in Spring (in a simplified form!).

public class SpringContextsApplication {

  public static void main(String[] args) throws Exception {
    try (var contextFromJava =new GenericApplicationContext()) {
      var reader =new ConfigurationClassBeanDefinitionReader(contextFromJava);
      reader.loadBeanDefinitions(Set.of(new ConfigurationClass(ApplicationConfig.class, "applicationContext")));
      contextFromJava.refresh();
      doGreeting(contextFromJava);
    }
  }

  private static void doGreeting(ApplicationContext ctx) {
    var greeter = ctx.getBean(Greeter.class);
    var person = ctx.getBean(Person.class);
    greeter.greet(person);
  }
}

This is more or less what happens when Spring finds a Java configuration class. However all of that is hidden inside the AnnotationConfigApplicationContext class.

public class SpringContextsApplication {

  public static void main(String[] args) throws Exception {
    try (var contextFromJava = new AnnotationConfigApplicationContext(ApplicationContext.class)) {
      doGreeting(contextFromJava);
    }
  }

  private static void doGreeting(ApplicationContext ctx) {
    var greeter = ctx.getBean(Greeter.class);
    var person = ctx.getBean(Person.class);
    greeter.greet(person);
  }

Using Java with Functional Bean Registration for Configuration

Another option with Java based configuration is to use a functional style of registering beans. The GenericApplicationContext has a couple of registerBean methods which allow for a functional style of bean registration. Instead of writing a configuration class, xml or properties file the registration is done by calling the methods. Underneath it will still create a BeanDefinition and use the passed in Supplier as a factory method for the bean instance.

public class SpringContextsApplication {

  public static void main(String[] args) throws Exception {
    try (var functionalContext = new GenericApplicationContext()) {
      functionalContext.registerBean("person", Person.class, () -> new Person("Marten Deinum"));
      functionalContext.registerBean("greeter", Greeter.class, SystemOutGreeter::new);
      functionalContext.refresh();
      doGreeting(functionalContext);
    }
  }

  private static void doGreeting(ApplicationContext ctx) {
    var greeter = ctx.getBean(Greeter.class);
    var person = ctx.getBean(Person.class);
    greeter.greet(person);
  }
}

The code above will register 2 beans, just as the preceeding samples. One Person and one Greeter but in a functional approach. What happens is that Spring will create a specialized BeanDefinition the ClassDerivedBeanDefinition which gets the Supplier passed in as the factory (or as internally called the instance supplier)

Conclusion

Now what is the conclusion when seeing 4 different configuration mechanisms. The first 2 mechanisms translate into the same as what is happening inside an @Bean annotated method, the latter is a more functional and plain Java approach

Based on the .(class) for property file based and class attribute for XML based configuration you can conclude that Spring does new <class-value-here>() itself. It then will call set<property-name-here>(<property-value-here>) just as in Java based configuration, or passing constructor arguments when using <constructor-arg> in the XML configuration).

Properties XML Java Java (Functional)
.(class)=Person class=Person Name of the class Passed in name or name of class when null
person.$0=Marten Deinum <constructor-arg name="name" value="Marten Deinum" new Person("Marten Deinum") () -> new Person("Marten Deinum")

For those interested the source can be found on GitHub.