Spring's Inferred Destroy Method

The Spring Framework is one of my favorite frameworks to use. It just works and it does what you would expect.

Except when it doesn’t.

This week I ran into a little known feature (at least, to me) called “inferred destroy”. This was not a behavior I expected and I had to go digging into the source code and documentation to figure out what was going on.

Spoiler alert, what I’m about to describe isn’t a bug. Rather, it’s something I didn’t understand about the Spring Framework. I’ve been using Spring for over ten years, but as with most things, there always something new to learn. My hope is that you learn from my experience and avoid a few hours of frustration in the future.

The Problem

Let’s start with an example.

Suppose you have two classes: a Spring bean and a configuration object. The Spring bean implements some part of the system. The configuration object creates that bean, using the @Bean annotation.

The code might look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class FooService {
private boolean running = false;

@PostConstruct
public void init() {
running = true;
System.out.println("Initializing FooService");
}

@PreDestroy
public void shutdown() {
System.out.println("Shutting down FooService");
if (!running) {
System.out.println("FooService isn't running");
}
running = false;
}
}

@Configuration
public class FooConfiguration {
@Bean
public FooService fooService() { return new FooService(); }
}

Looks reasonable enough, right? The FooService class is created by the FooConfiguration class, Spring invokes the init() method, since it has a @PostConstruct annotation, and the application goes on its merry way. Life is good.

But a curious thing happens when the application shuts down. The final output looks like this:

1
2
3
4
Initializing FooService
Shutting down FooService
Shutting down FooService
FooService isn't running

What the heck is going on? The shutdown() method is obviously getting called twice! Why would that be?

The Cause

After spending some time in the debugger, I found the class responsible in the Spring Framework. This led me to the documentation, which explained what was happening.

Spring is being a bit too helpful.

When creating beans using @Bean annotated methods, Spring attempts to automatically detect and register a “destroy” method for you. This means that Spring looks for a no-argument close() or shutdown() method somewhere in the class hierarchy, whether it’s declared static or not. If one is found, it will be called when the bean is destroyed.

By adding the @PreDestroy annotation, I effectively told Spring to call my shutdown() method twice: once due to the @PreDestroy annotation and once because the method happens to be called shutdown().

This is all in the Spring documentation, but requires a bit of effort to connect the dots. The main Spring documentation states:

Destroy methods are called in the same order:

  • Methods annotated with @PreDestroy
  • destroy() as defined by the DisposableBean callback interface
  • A custom configured destroy() method

If you combine this with the Javadoc for the @Bean annotation, you get the behavior I described above.

To fix this, I have three options:

Solution 1: Delegate to Inferred Destroy

First, I could remove the @PreDestroy annotation. This way, I rely on the “destroy method inference” to take over.

This approach isn’t as appealing to me for a few reasons:

  • It is less clear. Obviously I didn’t know that the shutdown() method would automatically get called by Spring.
  • It is less maintainable. Not only is it less clear, itself a maintainability problem, but now I have to keep an eye on Spring’s release notes to determine if this behavior changes over time.
  • It is fragile. There is not a corresponding “inferred init” pattern. So the class has a @PostConstruct, but there isn’t a corresponding @PreDestroy. Chances are good that another developer might look at the code and think the @PostConstruct annotation was accidentally left off.

Solution 2: Disable the Default Inference

Second, I could explicitly set the destroyMethod on the @Bean annotation to blank, like this:

1
2
3
4
5
@Configuration
public class FooConfiguration {
@Bean(destroyMethod="")
public FooService fooService() { return new FooService(); }
}

The default value for the destroyMethod is “(inferred)”. By setting it to blank, we explicitly tell Spring not to register a destroy method with the bean.

This is better. We keep the balanced @PostConstruct and @PreDestroy in the class, but we have to remember to override the destroyMethod attribute in the @Bean annotation.

Solution 3: Use Component Scanning

The final way to solve the issue is to simply let the class get picked up by your application’s component scanning. To do this, add a @Service annotation to the FooClass and make sure the class is in the list of packages scanned by Spring. Beans found during component scanning do not have the same “inferred destroy method” behavior as the @Bean annotation does.

This solves the issues with both of the previous solutions. It keeps the @PostConstruct and @PreDestroy annotations and there is no @Bean annotation to update.

Of course, this won’t work if you don’t have control over the class in question.  In that situation it probably doesn’t have the Spring annotations either. But if it does, the second solution is your only option.

I suspect you’d rather spend your time solving business problems. Hopefully this prevents you from wasting a bunch of time, so you can spend your time much more productively. Instead you can track down a different obscure bug introduced by a different library you’re using.

Question: Have you run into this problem? What did you do to solve it?