@PreDestroy and @PostConstruct in Spring: Managing Bean Lifecycle

Author:

These annotations are called at specific moments in the bean lifecycle.

They allow you to define methods executed after a bean is created and before the beans are destroyed.

Both annotations come from Jakarta EE and can be used in Spring projects.

They are useful for managing resources that are only used in the bean’s life cycle scope.

Let’s see a practical example:

We have a CustomNotificationService bean that uses an ExternalMessageSender

Before sending messages, you need to create a connection to an external API and authenticate.

So our ExternalMessageSender should look like this:

public class ExternalMessageSender {

    public void authenticate(String apiKey, String secret) {
        log.info("Called on bean creation");
    }

    public void sendMessage(String recipient, String content) {
        log.info("Message sent to " + recipient + ": " + content + " when processing the request");
    }

    public void closeConnection() {
        log.info("Called on bean destruction");
    }
}

Now, let’s create the CustomNotificationService bean, where the fun happens.

This bean will be request-scoped, so it becomes easier to see the bean lifecycle.

The scope will be requested so the bean will be available only in the It will be created when a request is made, and destroyed after the request is done.

@Service
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CustomNotificationService {

    private ExternalMessageSender messageSender;

    @PostConstruct
    public void initialize() {
        messageSender = new ExternalMessageSender("<https://api.example.com/messages>");
        messageSender.authenticate("api_key", "secret");
    }

    public void sendNotification(String userId, String message) {
        messageSender.sendMessage(userId, message);
    }

    @PreDestroy
    public void cleanup() {
        messageSender.closeConnection();
    }
}


So we used the initialize() method as @PostConstruct after the bean was created.

We used the cleanup() method will be used before the bean was destroyed.

And we can use the resource we created in Bean in all the other methods.

To exercise this code, we can create this controller:

// NotificationController class
@RestController
public class NotificationController {
    // ... (Autowired notificationService) ...

    @PostMapping("/send-notification")
    public String sendNotification(@RequestBody NotificationRequest request) {
        String userId = request.userId();
        String message = request.message();
        notificationService.sendNotification(userId, message);
        return "Notification sent successfully!";
    }
}

// NotificationRequest record
public record NotificationRequest(
        String userId,
        String message
) {}


Calling the endpoint /send-notification, you’ll see the following output in logs, demonstrating the methods being executed at each stage of the bean’s lifecycle:

2024-06-27T21:30:48.287-03:00 INFO 14944 --- [nio-8080-exec-3] c.s.m.postandpre.ExternalMessageSender   : Called on bean creation
2024-06-27T21:30:48.287-03:00 INFO 14944 --- [nio-8080-exec-3] c.s.m.postandpre.ExternalMessageSender   : Message sent to 123: Hello world @PostConstruct and @PreDestroy. when processing the request
2024-06-27T21:30:48.289-03:00 INFO 14944 --- [nio-8080-exec-3] c.s.m.postandpre.ExternalMessageSender   : Called on bean destruction


Conclusion

In this blog post, you learned how to leverage the @PostConstruct and @PreDestroy annotations to add custom behavior during the lifecycle of your Spring beans.

If you found this topic interesting, follow me for more insights into Spring annotations and other valuable tips!

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

Willian Ferreira Moya | LinkedIn

Leave a Reply

Your email address will not be published. Required fields are marked *