Spring Essentials: How to Use the @Bean Annotation for Custom Services

In our application, we have a lot of objects. These objects are components of our application. When we want Spring to create, manage, and inject these objects, they are considered Spring beans. The @Bean annotation is a method-level annotation that we can use to tell Spring that we want it to manage the instance for us. When starting the application context, Spring will look for these @Bean methods and add them to the IoC container to manage their lifecycle so that other components can inject and use them. This annotation is usually placed in configuration classes. As we know, Spring already has some auto-configuration for resources like Spring Kafka, Spring Cache, etc. But if you want to create and manage a custom resource, like a custom notification service, using the @Bean to make this custom bean available in your application is the way to go. In some cases, we can use the @Bean annotation to customize some auto-configured beans. This allows us to replace the default configuration with a modified version that suits our needs. Let's take a look. Creating a Custom Bean In this scenario, our application must use a custom SMTP service to send emails. We can create a method to instantiate the service as we need and return the object. Since we want this to become a bean, we annotate the method with @Bean to tell Spring to put this object in the Spring IoC as a bean. package org.spring.mastery.config; import org.spring.mastery.service.SmtpNotificationService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class NotificationConfig { @Bean public SmtpNotificationService smtpNotificationService() { return new SmtpNotificationService("smtp.example.com", 587, "username", "password"); } } The class we want to be a custom bean: package org.spring.mastery.service; public class SmtpNotificationService { public SmtpNotificationService(String host, int port, String username, String password) { //add fake behavior System.out.println(STR."Connecting to SMTP server at \{host}:\{port}"); System.out.println(STR."Authenticating with username: \{username}"); System.out.println("Sending a test email to verify connection..."); System.out.println("Email sent successfully. Connection verified."); } public void sendEmail(String mail, String subject, String message) { System.out.println(STR."Sending email to \{mail}..."); System.out.println(STR."Email subject: \{subject}"); System.out.println(STR."Email message: \{message}"); System.out.println("Email sent successfully."); } } Using the Custom Bean Now we can inject and use the custom bean that we created. package com.example.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class NotificationService { private final SmtpNotificationService smtpNotificationService; @Autowired public NotificationService(SmtpNotificationService smtpNotificationService) { this.smtpNotificationService = smtpNotificationService; } public void sendNotification(String message) { smtpNotificationService.sendEmail("recipient@example.com", "Subject", message); } } If we run the project, we should see that spring created the bean for us and the setup of the bean should appear on the console like this: 2025-04-11T10:04:25.296-03:00 INFO 11240 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' Connecting to SMTP server at smtp.example.com:587 Authenticating with username: username Sending a test email to verify connection... Email sent successfully. Connection verified. Modifying a Bean Some spring libraries can provide ready-to-use beans for you to use. Like the spring Kafka. You only need to add the dependencies and the respective configuration properties, and you are good to go. org.springframework.kafka spring-kafka 3.3.4 # Kafka Configuration spring.kafka.bootstrap-servers=localhost:9092 spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer package org.spring.mastery.service; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; @Service public class KafkaService { private final KafkaTemplate kafkaTemplate; public KafkaService(KafkaTemplate kafkaTemplate) { this.kafkaTemplate = kafkaTemplate; } public void sendMessage(String topic, String message) { kafkaTemplate.send(topic, message); } } But if we want to customize it, we can use the @Bean to create a more customi

Apr 29, 2025 - 17:35
 0
Spring Essentials: How to Use the @Bean Annotation for Custom Services

In our application, we have a lot of objects. These objects are components of our application. When we want Spring to create, manage, and inject these objects, they are considered Spring beans.

The @Bean annotation is a method-level annotation that we can use to tell Spring that we want it to manage the instance for us.

When starting the application context, Spring will look for these @Bean methods and add them to the IoC container to manage their lifecycle so that other components can inject and use them.

This annotation is usually placed in configuration classes. As we know, Spring already has some auto-configuration for resources like Spring Kafka, Spring Cache, etc.

But if you want to create and manage a custom resource, like a custom notification service, using the @Bean to make this custom bean available in your application is the way to go.

In some cases, we can use the @Bean annotation to customize some auto-configured beans. This allows us to replace the default configuration with a modified version that suits our needs.

Let's take a look.

Creating a Custom Bean

In this scenario, our application must use a custom SMTP service to send emails. We can create a method to instantiate the service as we need and return the object. Since we want this to become a bean, we annotate the method with @Bean to tell Spring to put this object in the Spring IoC as a bean.

package org.spring.mastery.config;

import org.spring.mastery.service.SmtpNotificationService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class NotificationConfig {

    @Bean
    public SmtpNotificationService smtpNotificationService() {
        return new SmtpNotificationService("smtp.example.com", 587, "username", "password");
    }
}

The class we want to be a custom bean:

package org.spring.mastery.service;

public class SmtpNotificationService {

    public SmtpNotificationService(String host, int port, String username, String password) {
        //add fake behavior
        System.out.println(STR."Connecting to SMTP server at \{host}:\{port}");
        System.out.println(STR."Authenticating with username: \{username}");
        System.out.println("Sending a test email to verify connection...");
        System.out.println("Email sent successfully. Connection verified.");
    }

    public void sendEmail(String mail, String subject, String message) {
        System.out.println(STR."Sending email to \{mail}...");
        System.out.println(STR."Email subject: \{subject}");
        System.out.println(STR."Email message: \{message}");
        System.out.println("Email sent successfully.");

    }
}

Using the Custom Bean

Now we can inject and use the custom bean that we created.

package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class NotificationService {

    private final SmtpNotificationService smtpNotificationService;

    @Autowired
    public NotificationService(SmtpNotificationService smtpNotificationService) {
        this.smtpNotificationService = smtpNotificationService;
    }

    public void sendNotification(String message) {
        smtpNotificationService.sendEmail("recipient@example.com", "Subject", message);
    }
}

If we run the project, we should see that spring created the bean for us and the setup of the bean should appear on the console like this:

2025-04-11T10:04:25.296-03:00  INFO 11240 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
Connecting to SMTP server at smtp.example.com:587
Authenticating with username: username
Sending a test email to verify connection...
Email sent successfully. Connection verified.

Modifying a Bean

Some spring libraries can provide ready-to-use beans for you to use. Like the spring Kafka. You only need to add the dependencies and the respective configuration properties, and you are good to go.


    org.springframework.kafka
    spring-kafka
    3.3.4

# Kafka Configuration
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
package org.spring.mastery.service;

import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class KafkaService {

    private final KafkaTemplate<String, String> kafkaTemplate;

    public KafkaService(KafkaTemplate<String, String> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void sendMessage(String topic, String message) {
        kafkaTemplate.send(topic, message);
    }
}

But if we want to customize it, we can use the @Bean to create a more customized bean to suit our application needs.

package org.spring.mastery.config;

import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.support.serializer.JsonSerializer;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class KafkaConfig {

    @Bean
    public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
        return new DefaultKafkaProducerFactory<>(configProps);
    }

    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        System.out.println("Create custom kafka bean");
        return new KafkaTemplate<>(producerFactory());
    }
}

The console will show that our bean was created:

2025-04-11T11:06:23.541-03:00  INFO 22032 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
Create custom kafka bean

Now you have learned how to use the @Bean annotation to tell Spring to handle your custom beans.

Explore this configuration and share your thoughts in the comments or on social media.

If you like this topic, make sure to follow me. In the following days, I’ll be explaining more about Spring annotations! Stay tuned!

Willian Moya (@WillianFMoya) / X (twitter.com)

Willian Ferreira Moya | LinkedIn