Spring BootXML-based Configuration
Spring Boot

XML-based Configuration

XML-based configuration was Spring's original configuration mechanism — declaring beans, dependencies, and wiring in applicationContext.xml files. While modern Spring Boot uses Java-based configuration and component scanning, understanding XML configuration is essential for working with legacy Spring applications, migrating existing systems, and understanding the concepts that underpin all of Spring's configuration models.

What Is XML-based Configuration?

XML-based configuration was Spring's first configuration model, introduced with Spring 1.0 in 2004. Beans, their dependencies, and all wiring were declared in XML files — typically named applicationContext.xml or beans.xml. The container reads these files at startup, creates BeanDefinitions from the XML declarations, and instantiates and wires beans accordingly. XML configuration is no longer the recommended approach for new projects. Spring Boot uses auto-configuration, component scanning, and Java-based @Configuration classes. However, XML configuration remains relevant because: - Millions of production Spring applications still use XML configuration - Enterprise codebases are often migrated incrementally — XML and Java config coexist - Understanding XML config explains the concepts behind all Spring configuration - Some Spring integrations (Spring Batch jobs, Spring Integration flows) still commonly use XML - Legacy maintenance requires reading and modifying XML configuration files Spring Boot fully supports XML configuration via @ImportResource — you can mix XML beans with auto-configured and Java-configured beans in any combination.

Basic Bean Declaration

The fundamental building block of XML configuration is the bean element. Each bean element declares one Spring-managed object — specifying its class, name, scope, and how its dependencies are provided.
XML
<?xml version="1.0" encoding="UTF-8"?>
<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
           https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- Basic bean — class is required, id is the bean name: -->
    <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
        <!-- Constructor argument — the strength parameter: -->
        <constructor-arg value="12" type="int"/>
    </bean>

    <!-- Bean with no constructor args — default constructor: -->
    <bean id="userRepository" class="com.example.repository.JpaUserRepository"/>

    <!-- Bean referencing another bean — ref attribute: -->
    <bean id="userService" class="com.example.service.UserService">
        <constructor-arg ref="userRepository"/>
        <constructor-arg ref="passwordEncoder"/>
        <constructor-arg ref="emailService"/>
    </bean>

    <!-- Bean with property injection (setter injection): -->
    <bean id="emailService" class="com.example.service.SmtpEmailService">
        <property name="host" value="smtp.gmail.com"/>
        <property name="port" value="587"/>
        <property name="username" value="myapp@gmail.com"/>
        <property name="ssl" value="true"/>
    </bean>

    <!-- Singleton scope (default — explicit declaration is redundant): -->
    <bean id="cacheService" class="com.example.service.CacheService" scope="singleton"/>

    <!-- Prototype scope — new instance on every getBean() call: -->
    <bean id="reportGenerator" class="com.example.service.ReportGenerator" scope="prototype"/>

    <!-- Lazy initialization — only created on first request: -->
    <bean id="analyticsService" class="com.example.service.AnalyticsService" lazy-init="true"/>

    <!-- Abstract bean — a template, cannot be instantiated directly: -->
    <bean id="baseService" abstract="true">
        <property name="maxRetries" value="3"/>
        <property name="timeout" value="5000"/>
    </bean>

    <!-- Bean inheriting from abstract template: -->
    <bean id="orderService"
          class="com.example.service.OrderService"
          parent="baseService">
        <!-- Inherits maxRetries and timeout from baseService -->
        <constructor-arg ref="orderRepository"/>
    </bean>

</beans>

Constructor and Setter Injection in XML

XML supports both constructor injection and setter injection. Constructor injection uses constructor-arg elements. Setter injection uses property elements. Both can reference other beans or provide literal values.
XML
<?xml version="1.0" encoding="UTF-8"?>
<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
           https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- Constructor injection — by index: -->
    <bean id="jwtService" class="com.example.security.JwtTokenService">
        <constructor-arg index="0" value="my-secret-key"/>
        <constructor-arg index="1" value="3600"/>
        <constructor-arg index="2" value="https://myapp.com"/>
    </bean>

    <!-- Constructor injection — by type: -->
    <bean id="reportService" class="com.example.service.ReportService">
        <constructor-arg type="java.lang.String" value="PDF"/>
        <constructor-arg type="int" value="100"/>
    </bean>

    <!-- Constructor injection — by name (most readable): -->
    <bean id="paymentService" class="com.example.service.PaymentService">
        <constructor-arg name="apiKey" value="sk_live_abc123"/>
        <constructor-arg name="timeout" value="30"/>
        <constructor-arg name="retryCount" value="3"/>
    </bean>

    <!-- Setter injection — property elements: -->
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mydb"/>
        <property name="username" value="root"/>
        <property name="password" value="secret"/>
        <property name="maximumPoolSize" value="10"/>
        <property name="minimumIdle" value="5"/>
        <property name="connectionTimeout" value="30000"/>
    </bean>

    <!-- Mixed — constructor for required, setter for optional: -->
    <bean id="notificationService" class="com.example.service.NotificationService">
        <constructor-arg ref="userRepository"/>
        <!-- Optional dependencies via setter: -->
        <property name="emailService" ref="emailService"/>
        <property name="smsService" ref="smsService"/>
        <property name="maxRetries" value="3"/>
    </bean>

    <!-- Injecting collections: -->
    <bean id="compositeValidator" class="com.example.validation.CompositeValidator">
        <!-- List of bean references: -->
        <constructor-arg>
            <list>
                <ref bean="emailValidator"/>
                <ref bean="phoneValidator"/>
                <ref bean="addressValidator"/>
            </list>
        </constructor-arg>
    </bean>

    <!-- Injecting a Map: -->
    <bean id="handlerRegistry" class="com.example.handler.HandlerRegistry">
        <property name="handlers">
            <map>
                <entry key="ORDER_CREATED" value-ref="orderCreatedHandler"/>
                <entry key="ORDER_CANCELLED" value-ref="orderCancelledHandler"/>
                <entry key="PAYMENT_RECEIVED" value-ref="paymentReceivedHandler"/>
            </map>
        </property>
    </bean>

    <!-- Injecting a Set: -->
    <bean id="roleManager" class="com.example.security.RoleManager">
        <property name="allowedRoles">
            <set>
                <value>ADMIN</value>
                <value>MANAGER</value>
                <value>USER</value>
            </set>
        </property>
    </bean>

    <!-- Injecting Properties (java.util.Properties): -->
    <bean id="mailConfig" class="com.example.mail.MailConfiguration">
        <property name="mailProperties">
            <props>
                <prop key="mail.smtp.host">smtp.gmail.com</prop>
                <prop key="mail.smtp.port">587</prop>
                <prop key="mail.smtp.auth">true</prop>
            </props>
        </property>
    </bean>

</beans>

XML Namespaces — context, tx, aop

Spring provides XML namespaces that add concise shorthand for common configuration tasks — component scanning, property loading, transaction management, and AOP. These namespaces dramatically reduce the verbosity of XML configuration.
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           https://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           https://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx
           https://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop
           https://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/task
           https://www.springframework.org/schema/task/spring-task.xsd">

    <!-- context namespace — component scanning: -->
    <context:component-scan base-package="com.example.myapp"/>
    <!-- Equivalent Java: @ComponentScan("com.example.myapp") -->

    <!-- context namespace — load external properties file: -->
    <context:property-placeholder location="classpath:application.properties"/>
    <!-- After this, use ${property.name} in value attributes -->

    <!-- Use loaded property values: -->
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
        <property name="jdbcUrl" value="${spring.datasource.url}"/>
        <property name="username" value="${spring.datasource.username}"/>
        <property name="password" value="${spring.datasource.password}"/>
    </bean>

    <!-- context namespace — annotation-driven configuration support: -->
    <context:annotation-config/>
    <!-- Enables @Autowired, @Required, @PostConstruct, @PreDestroy processing -->
    <!-- NOT needed if context:component-scan is present — it includes this -->

    <!-- tx namespace — annotation-driven transaction management: -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <!-- Enables @Transactional on beans -->
    <!-- Equivalent Java: @EnableTransactionManagement -->

    <bean id="transactionManager"
          class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <!-- aop namespace — declarative AOP: -->
    <aop:config>
        <!-- Pointcut — matches all methods in service package: -->
        <aop:pointcut id="serviceLayer"
                      expression="execution(* com.example.service.*.*(..))"/>

        <!-- Advisor — apply advice to pointcut: -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceLayer"/>
    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="get*"  read-only="true"/>
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="*"     propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!-- task namespace — scheduling and async: -->
    <task:annotation-driven executor="taskExecutor" scheduler="taskScheduler"/>
    <!-- Enables @Scheduled and @Async -->

    <task:executor id="taskExecutor" pool-size="5-20" queue-capacity="100"/>
    <task:scheduler id="taskScheduler" pool-size="5"/>

</beans>

Splitting Configuration Across Multiple Files

Large XML configurations are split across multiple files — one per layer or concern. Spring provides two mechanisms for composing multiple XML files: import elements within XML and loading multiple files when creating the ApplicationContext.
XML
<!-- applicationContext.xml — root configuration that imports others: -->
<?xml version="1.0" encoding="UTF-8"?>
<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
           https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- Import other configuration files: -->
    <import resource="classpath:config/datasource-config.xml"/>
    <import resource="classpath:config/security-config.xml"/>
    <import resource="classpath:config/cache-config.xml"/>
    <import resource="classpath:config/messaging-config.xml"/>

    <!-- Beans defined here can reference beans from imported files: -->
    <bean id="userService" class="com.example.service.UserService">
        <constructor-arg ref="userRepository"/>   <!-- defined in datasource-config.xml -->
        <constructor-arg ref="emailService"/>     <!-- defined in messaging-config.xml -->
    </bean>

</beans>

<!-- config/datasource-config.xml: -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           https://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           https://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:application.properties"/>

    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
        <property name="jdbcUrl" value="${spring.datasource.url}"/>
        <property name="username" value="${spring.datasource.username}"/>
        <property name="password" value="${spring.datasource.password}"/>
        <property name="maximumPoolSize" value="${datasource.pool.max:10}"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg ref="dataSource"/>
    </bean>

    <bean id="userRepository" class="com.example.repository.JdbcUserRepository">
        <constructor-arg ref="jdbcTemplate"/>
    </bean>

</beans>

<!-- Loading multiple XML files when creating ApplicationContext manually: -->
<!-- (Non-Boot — standard Spring) -->
ApplicationContext context = new ClassPathXmlApplicationContext(
    "applicationContext.xml",
    "config/datasource-config.xml",
    "config/security-config.xml"
);

<!-- Or with wildcard: -->
ApplicationContext context = new ClassPathXmlApplicationContext(
    "classpath*:config/*-config.xml"
);

Lifecycle Callbacks and Bean Post-Processing in XML

XML configuration supports the full bean lifecycle — init and destroy methods, BeanPostProcessor registration, and factory beans for complex creation logic.
XML
<?xml version="1.0" encoding="UTF-8"?>
<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
           https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- Lifecycle callbacks — init-method and destroy-method: -->
    <bean id="connectionPool"
          class="com.example.db.DatabaseConnectionPool"
          init-method="initialize"
          destroy-method="shutdown">
        <property name="url" value="${db.url}"/>
        <property name="poolSize" value="10"/>
    </bean>

    <!-- Default init and destroy methods for all beans in this file: -->
    <beans default-init-method="init" default-destroy-method="cleanup">
        <!-- All beans in this scope will call init() after creation -->
        <!-- and cleanup() on context close, if those methods exist -->
        <bean id="serviceA" class="com.example.ServiceA"/>
        <bean id="serviceB" class="com.example.ServiceB"/>
    </beans>

    <!-- Factory bean — delegate creation to another bean: -->
    <!-- FactoryBean interface creates complex objects: -->
    <bean id="sessionFactory"
          class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="com.example.model"/>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</prop>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">validate</prop>
            </props>
        </property>
    </bean>
    <!-- sessionFactory bean is the Hibernate SessionFactory, not the FactoryBean itself -->
    <!-- Use &amp;sessionFactory to get the FactoryBean itself (rare) -->

    <!-- BeanPostProcessor — registered like any other bean: -->
    <bean class="com.example.processor.CustomBeanPostProcessor"/>
    <!-- Spring detects BeanPostProcessor implementations and applies them automatically -->

    <!-- DependsOn — ensure creation order: -->
    <bean id="userRepository"
          class="com.example.repository.UserRepository"
          depends-on="databaseMigration">
        <constructor-arg ref="dataSource"/>
    </bean>

    <bean id="databaseMigration" class="com.example.migration.FlywayMigration">
        <constructor-arg ref="dataSource"/>
    </bean>

    <!-- Aliases — multiple names for the same bean: -->
    <alias name="userService" alias="accountService"/>
    <alias name="userService" alias="memberService"/>
    <!-- All three names refer to the same singleton bean -->

</beans>

Using XML Configuration in Spring Boot

Spring Boot uses Java-based configuration by default, but fully supports loading XML configuration alongside it via @ImportResource. This is the standard approach during migration from legacy XML-based applications.
Java
// Load XML configuration into a Spring Boot application:
@SpringBootApplication
@ImportResource("classpath:applicationContext.xml")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// Load multiple XML files:
@SpringBootApplication
@ImportResource({
    "classpath:config/datasource-config.xml",
    "classpath:config/security-config.xml",
    "classpath:config/legacy-services.xml"
})
public class Application { }

// Load XML from a specific @Configuration class (more targeted):
@Configuration
@ImportResource("classpath:legacy/batch-jobs.xml")
public class BatchConfig {
    // Additional Java-based config for the batch module
    @Bean
    public JobLauncher jobLauncher(JobRepository jobRepository) throws Exception {
        SimpleJobLauncher launcher = new SimpleJobLauncher();
        launcher.setJobRepository(jobRepository);
        launcher.afterPropertiesSet();
        return launcher;
    }
}

// Beans from XML are fully available for injection in Spring Boot beans:
@Service
public class UserService {

    // This bean is defined in legacy-services.xml — fully injectable:
    @Autowired
    private LegacyUserRepository legacyUserRepository;

    // Mix with auto-configured beans:
    @Autowired
    private PasswordEncoder passwordEncoder;    // auto-configured by Spring Boot Security

    public User migrateUser(LegacyUser legacy) {
        User user = new User(legacy.getEmail(), legacy.getName());
        user.setPassword(passwordEncoder.encode(legacy.getRawPassword()));
        return userRepository.save(user);
    }
}

// XML beans can inject Spring Boot auto-configured beans too:
// In legacy-services.xml:
// (Spring Boot's auto-configured dataSource is available by name)
//
// bean id="legacyDao" class="com.example.LegacyDao"
//   property name="dataSource" ref="dataSource"   -> Spring Boot's auto-configured bean
// /bean

Migrating from XML to Java Configuration

Migrating XML configuration to Java-based configuration is a common task when modernizing Spring applications. The migration is done incrementally — bean by bean, file by file — with both XML and Java config coexisting throughout.
Java
// BEFORE — XML configuration:
// applicationContext.xml:
//
// bean id="passwordEncoder"
//       class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"
//   constructor-arg value="12" type="int"
// /bean
//
// bean id="userRepository" class="com.example.repository.JdbcUserRepository"
//   constructor-arg ref="jdbcTemplate"
// /bean
//
// bean id="userService" class="com.example.service.UserService"
//   constructor-arg ref="userRepository"
//   constructor-arg ref="passwordEncoder"
// /bean

// AFTER — Equivalent Java configuration:
@Configuration
public class AppConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12);
    }

    @Bean
    public UserRepository userRepository(JdbcTemplate jdbcTemplate) {
        return new JdbcUserRepository(jdbcTemplate);
    }

    @Bean
    public UserService userService(UserRepository userRepository,
                                   PasswordEncoder passwordEncoder) {
        return new UserService(userRepository, passwordEncoder);
    }
}

// XML namespace to Java annotation mapping:
//
// context:component-scan     → @ComponentScan
// context:property-placeholder → @PropertySource + @Value or @ConfigurationProperties
// context:annotation-config  → @Configuration (implied)
// tx:annotation-driven       → @EnableTransactionManagement
// aop:config                 → @EnableAspectJAutoProxy + @Aspect classes
// task:annotation-driven     → @EnableAsync + @EnableScheduling
// mvc:annotation-driven      → @EnableWebMvc (or Spring Boot auto-config)

// INCREMENTAL MIGRATION STRATEGY:
// Step 1 — Add @ImportResource to load existing XML alongside new Java config
@SpringBootApplication
@ImportResource("classpath:legacy/applicationContext.xml")
public class Application { }

// Step 2 — Migrate one bean at a time to Java @Configuration
// Remove the bean from XML, add @Bean method to a @Configuration class
// Verify the application still works after each migration

// Step 3 — Replace XML namespaces with annotations
// Remove tx:annotation-driven from XML
// Add @EnableTransactionManagement to a @Configuration class

// Step 4 — Remove @ImportResource once all XML is migrated

// Useful tool — Spring Boot's conditions report shows all beans
// and where they were configured (XML vs Java):
// java -jar app.jar --debug
// Look for "XML Bean Definition" vs "Configuration Class" in the report

XML Configuration vs Java Configuration — Full Comparison

A direct comparison of XML and Java configuration for the same beans — showing the verbosity difference and the trade-offs of each approach.
Java
// ── DATASOURCE CONFIGURATION ─────────────────────────────────────

// XML:
// bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
//   property name="jdbcUrl" value="${spring.datasource.url}"
//   property name="username" value="${spring.datasource.username}"
//   property name="password" value="${spring.datasource.password}"
//   property name="maximumPoolSize" value="10"
// /bean

// Java:
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariDataSource dataSource(DataSourceProperties props) {
    return props.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}

// ── TRANSACTION MANAGEMENT ────────────────────────────────────────

// XML:
// tx:annotation-driven transaction-manager="transactionManager"
// bean id="transactionManager"
//       class="org.springframework.orm.jpa.JpaTransactionManager"
//   property name="entityManagerFactory" ref="entityManagerFactory"
// /bean

// Java:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf);
    }
}

// ── COMPONENT SCANNING ────────────────────────────────────────────

// XML:
// context:component-scan base-package="com.example.myapp"

// Java:
@ComponentScan("com.example.myapp")

// ── AOP ──────────────────────────────────────────────────────────

// XML (verbose):
// aop:config
//   aop:pointcut id="serviceLayer"
//       expression="execution(* com.example.service.*.*(..))"
//   aop:aspect ref="loggingAspect"
//     aop:around method="logExecutionTime" pointcut-ref="serviceLayer"
// /aop:config

// Java (concise):
@Aspect
@Component
public class LoggingAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object logExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        log.info("{} took {}ms", pjp.getSignature().getName(),
                 System.currentTimeMillis() - start);
        return result;
    }
}

// ── TRADE-OFFS SUMMARY ────────────────────────────────────────────

// XML advantages:
// - No recompilation needed to change configuration
// - Configuration separate from code (debated benefit)
// - Readable by non-developers (XML is universal)
// - Some legacy Spring tooling integrates with XML

// Java advantages:
// - Type-safe (compile-time errors vs runtime failures)
// - Full IDE support (navigation, refactoring, auto-complete)
// - Can use loops, conditions, factory methods in bean creation
// - Less verbose for complex beans
// - Easier to test (just instantiate the @Configuration class)
// - No namespace declarations and schema URLs
// - Spring Boot auto-configuration is 100% Java-based