The Phantom Bean


Monday morning,

I was sipping coffee. On my desk. in my office

Sarah, along with another Junior Developer, Tom, came to me.

"Service works on my laptop. Crashes in staging. Same code. No idea why!" - Said Tom

I checked the log.

in the staging:

ERROR - No qualifying bean of type 'EmailService' available
ERROR - expected at least 1 bean which qualifies as autowire candidate

But Tom's Local logs:

INFO - Started Application in 8.2 seconds
INFO - EmailService initialized successfully

Same code.

Same branch.

Different results.  šŸ¤·

I checked Tom's code.

// Main application
package com.company.app;
​
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

The service Package

com.company.services.email;
​
@Service
public class EmailService {
public void send(String to, String message) {
// Send email
}
}

The controller

package com.company.app.controllers;
​
@RestController
public class UserController {
​
@Autowired
private EmailService emailService; // Bean not found!
​
@PostMapping("/register")
public String register() {
emailService.send("user@example.com", "Welcome!"); return "ok";
}
}

"What's the difference between local and staging?" Sarah asked.

Tom shrugged.

"Nothing. Same Dockerfile."

I checked the Dockerfile.

FROM openjdk:17
COPY target/app.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

Here is how Tom ran it locally:

mvn spring-boot:run

There it was!!!

The Problem:

In the IDE, Tom ran from the project root. Maven scanned everything.

In Docker, the JAR was built with:

mvn clean package

I told Sarah to check the JAR contents.

She did:

jar tf target/app.jar | grep EmailService

NOTHING

The `EmailService` class wasn't in the JAR at all.

WHY?

She looked at the project structure

project/

​
|___src/
| |___main/
| |____java
| |___com/company/app/
| |__Application.java
| |__controllers/
| |__UserController.java
|___services
|___src/
|___main/
|___java
|___com/company/services/email/
|___EmailService.java

`EmailService` was in a seperate module, outside the main `src` directory!

Maven's `package` goal only bundled the main module.

The IDE classpath included everything.

The quick fix:

Move the service

src/
|___main/
|____java
|___com/company/app/
|__Application.java
|__controllers/
| |__UserController.java
|___services
|___EmailService.java

Rebuild

mvn clean package
java -jar target/app.jar
​
​
INFO - Started Application in 8.2 seconds

It WORKED!!! šŸ˜…

The better fix

But Sarah noticed another issue. Even with the correct structure:

package com.company.app;
​
@SpringBootApplication // Only scans com.company.app.*
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Can you find this?

It only scans the com.company.app*

If someone later moves this `EmailService` to com.company.external.services

It would break again.

So we have to make it explicit:

@SpringBootApplication
@ComponentScan(basePackages = {
"com.company.app",
"com.company.services"
})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

The Debug Command

I showed them a trick:

# Check what's actually in your JAR
jar tf target/app.jar | grep -i service
​
# Or search for any class
jar tf target/app.jar | grep EmailService

And another one:

// Add this temporarily to see what Spring found
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext ctx =
SpringApplication.run(Application.class, args);
String[] beans = ctx.getBeanDefinitionNames();
System.out.println("=== ALL BEANS ===");
Arrays.stream(beans)
.filter(name -> name.contains("email"))
.forEach(System.out::println);
}
}

NOTE: This one is temporary. dont forget to remove it in the production.

No, if `EmailService` appears in the list, Spring found it.

The takeaway:

"It works on my machine" usually means:

  • Different classpath
  • Different component scan paths
  • Different build process

Always check:

  1. Is the class in the JAR? (jar tf)
  2. Is Spring Scanning that package? (@ComponentScan)
  3. Build the same way locally as in prod (use Docker locally)

Quick Test:

# Build like prod

mvn clean package
​
# Run the JAR (not mvn spring-boot:run)
java -jar target/app.jar

If it fails locally now, you found the issue.

What's next:

The Memory Leak That Wasn't

Heap dumps point to Spring's cache. 8GB of data. But clearing the cache changes nothing.​
See you next week.

another word.

Did you subscribe to my YouTube channel?

If yes. Thank you.

if not... You can try...

I upload practical video related to Spring Framework.

Why not try?

​take a look here​

See you next.

Thank you

Iftekhar Hossain

Learn With Iftekhar

I’m Iftekhar — a developer sharing what I learn about Java, Spring Boot, Spring Security, and related backend technologies like Docker, Kubernetes, and Kafka.Each week, I send one practical email with code examples, mini-projects, and real-world lessons to help you grow as a backend developer.

Read more from Learn With Iftekhar

It was 3 am. Cold night. I was in deep sleep. That's when Sarah called me. "Payment service down. It's 500 errors!!" I was terrified. Customers couldn't buy anything. My sleep... just gone. I opened my computer.... Logged on to the server to see what happened. The log showed: ERROR - BeanCreationException: Error creating bean 'paymentProcessor' ERROR - Could not resolve placeholder 'stripe.api.secret' I was exhausted. After a loooong week of development, I just wanted to take some rest. but...

Last mail was about 'How to Use Java Records to Write Better and More Efficient Code'. But Is it really Immutable? Java often receives criticism for being too formal, requiring developers to write a lot of code even for simple tasks. It has some good sides like, it making Java code more readable and at the same time helping the developer to write code that has fewer bugs. In some instances, however, it creates unnecessary overhead. The worst case scenario is when there is a need for a data...

Java Records

How to Use Java Records to Write Better and More Efficient Code JAVA is one of the most popular and widely used programming languages. In many cases, it is the first choice for enterprise application development. Even now billions of devices are powered by JAVA. If you are reading this article on your Android — you got it! This is also run by JAVA! Oracle, the company who is responsible for developing and maintaining the JAVA language itself, introduces new features on a regular basis to make...