Last week I wrote a post about moving my Spring Boot project from MySQL to MongoDB. Somewhere in the middle of it I wrote a warning that sounded very confident.
I said: if you import @Id from jakarta.persistence instead of the Spring Data one, your ID mapping breaks and findById() stops working.
Then I went back and actually tried it.
It worked. Everything saved, everything loaded, nothing complained.
So I had to sit down and figure out why my broken code was not broken. What I found is more useful than the warning I gave, so here it is.
The two @id annotations
Java has this annoying situation where two completely different libraries give you an annotation with the same name:
import jakarta.persistence.Id; // this is the JPA one
import org.springframework.data.annotation.Id; // this is the Spring Data one
In your class both look the same:
@Id
private String id;
You cannot tell which one you used by looking at it. Only the import line tells you. And your IDE will pick one for you when you press the auto-import shortcut, usually whichever it saw first.
Here is my Task class:
@Document(collection = "app_tasks")
public class Task {
@Id
private String id;
private String title;
private String status = "PENDING";
}
I had the wrong import here for a while. I never noticed, because nothing went wrong.
Why nothing went wrong
Spring Data MongoDB has a backup plan I did not know about.
A field becomes the _id in MongoDB if either of these is true:
- It has
@Idfromorg.springframework.data.annotation - It has no recognised annotation, but the field is named
idLook at rule 2 again. That is what saved me.
Spring Data saw my JPA @Id and did not recognise it. Not its annotation, so it ignored it. But then it looked at my field name, saw id, and mapped it to _id anyway.
My wrong import did nothing at all. The field name did all the work.
When it does break
The backup plan only works if the field is called id. Change the name and it is gone.
@Document(collection = "app_tasks")
public class Task {
@Id // still the wrong import
private String taskId; // and now not called "id"
private String title;
}
Now neither rule applies. The annotation is not recognised and taskId is just a normal name. So Spring Data treats taskId as a regular field, and MongoDB creates its own separate _id that your Java object never sees.
And here is the bad part: nothing tells you.
No error. No warning. The app starts fine, saves work fine. But:
-
taskIdcomes backnullon every record you read -
findById()returns empty for records you can clearly see in the database - Every document has a hidden
_idyour code cannot touch You only find out when you try to fetch one specific record and it is not there. Then you spend an evening thinking your query is wrong, when the problem was an import line you wrote weeks ago.
Check your project right now
Open your model class. Look at the import, not the annotation.
import org.springframework.data.annotation.Id; // correct for MongoDB
import jakarta.persistence.Id; // wrong here
If yours says jakarta.persistence, change it. Even if your app works today, it is only working because of the field name.
Second check. Save one record, then look at it in MongoDB Compass or the shell:
db.app_tasks.findOne()
If you see an _id and a separate taskId sitting next to each other, your mapping is broken. When it is correct, there is only _id.
A way to avoid the whole problem
If you want to stop worrying about which @Id you imported, Spring Data MongoDB has its own annotation:
import org.springframework.data.mongodb.core.mapping.MongoId;
@MongoId
private String taskId;
@MongoId only exists in the MongoDB module. There is no JPA version, so your IDE cannot import the wrong one. Nice option if your project touches both JPA and MongoDB.
What I actually took from this
The bug I warned everyone about was not the real bug. The real problem was simpler: I did not know why my own code worked.
My import was wrong. My project ran fine. I would have carried that mistake into every future project and never known, until the day I renamed a field and lost an evening to it.
Code running is not the same as code being right. I keep learning this one.
Short version:
- Spring Data MongoDB maps
_idfrom the@Idannotation, or from a field just being namedid - The name rule hides a wrong import, so the wrong one often looks fine
- Rename the field and it breaks quietly — no error, just empty results
- Check the import line, not the annotation
-
@MongoIdavoids the whole mess Has this happened to you? Code that worked, but not for the reason you thought? I would like to hear it.
Top comments (0)