I just started developing some app in Java with spring-data-mongodb and came across some issue that I haven't been able to solve:
Have a couple of document beans like this:
@Document(collection="myBeanBar")
public class BarImpl implements Bar {
   String id;
   Foo foo;
   // More fields and methods ...
}
@Docuemnt
public class FooImpl implements Foo {
   String id;
   String someField;
   // some more fields and methods ...
}
And I have a repository class with a method that simply invokes a find similar to this:
public List<? extends Bar> findByFooField(final String fieldValue) {
    Query query = Query.query(Criteria.where("foo.someField").is(fieldValue));
    return getMongoOperations().find(query, BarImpl.class);
}
Saving a Bar works just fine, it would save it in mongo along with the "_class" attribute for both Foo and Bar. However, finding by some attribute in Foo would throw an exception like this:
Exception in thread "main" java.lang.IllegalArgumentException: No property someField found on test.Foo!
    at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentPropertyPath(AbstractMappingContext.java:225)
    at org.springframework.data.mongodb.core.convert.QueryMapper.getPath(QueryMapper.java:202)
    at org.springframework.data.mongodb.core.convert.QueryMapper.getTargetProperty(QueryMapper.java:190)
    at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedObject(QueryMapper.java:86)
    at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1336)
    at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1322)
    at org.springframework.data.mongodb.core.MongoTemplate.find(MongoTemplate.java:495)
    at org.springframework.data.mongodb.core.MongoTemplate.find(MongoTemplate.java:486)
Which, after some digging, makes some sense, since nowhere in the query is the sub-document concrete type being specified, and the Entity Information of Bar says the type of foo is Foo (not FooImpl), which in turn can not have properties cause it is an interface.
My question is: Is there a way to specify it or work-around this issue without declaring the sub-document type as a concrete type?
I've been googling it for a couple of days and looking at the documentation and API and the source code but I can not find a clear way to do it. I'd really appreciate your help.
Thank you very much.
I had a similar problem, I have a class that implements an interface and when I use findAll I get the error: 
org.springframework.data.mapping.model.MappingInstantiationException: Could not instantiate bean class [test.MetaClasse]: Specified class is an interface.
After debugging SpringData code, I realized that Mapper uses @TypeAlias to discover the type it has to instantiate, so I just put @TypeAlias("FullClassName") on my implementations of test.MetaClasse and it worked!
I tested with your situation and it will work!
Like mentioned in this comment, the solution with having full class name in the type alias is imperfect as it might make refactoring cumbersome.
Instead you can just configure type mappings and make it work automagically. Here's how:
First you'll need to annotate BarImpl and FooImpl with @TypeAlias. It doesn't have to be a full class name, could be anything else. For example @TypeAlias("bar_impl") and @TypeAlias("foo_impl") respectively.
Then we’re going to need the reflections library. Pick the latest version for the build tool of your choice here.
For example with Gradle:
implementation("org.reflections:reflections:0.10.2")
Now we’re going to need a small extension to DefaultMongoTypeMapper to make it easy to configure and instantiate. Here’s how it would look in Kotlin:
class ReflectiveMongoTypeMapper(
    private val reflections: Reflections = Reflections("com.example")
) : DefaultMongoTypeMapper(
    DEFAULT_TYPE_KEY,
    listOf(
        ConfigurableTypeInformationMapper(
            reflections.getTypesAnnotatedWith(TypeAlias::class.java).associateWith { clazz ->
                getAnnotation(clazz, TypeAlias::class.java)!!.value
            }
        ),
        SimpleTypeInformationMapper(),
    )
)
where com.example is either your base package or the package with MongoDB models.
This way we will find all classes annotated with @TypeAlias and register alias to type mappings.
Next we'll need to adjust the app's mongo configuration a bit. The configuration has to extend AbstractMongoClientConfiguration and we need to override method mappingMongoConverter to make use of the mapper we created before. It should look like this:
override fun mappingMongoConverter(
    databaseFactory: MongoDatabaseFactory,
    customConversions: MongoCustomConversions,
    mappingContext: MongoMappingContext,
) = super.mappingMongoConverter(databaseFactory, customConversions, mappingContext).apply {
    setTypeMapper(ReflectiveMongoTypeMapper())
}
Done!
Now all alias to type mappings will be registered automatically on context startup and all your polymorphic fields will work just fine.
You can check the full code example on GitHub.
Also, here's a blog post where you can read about the root cause of this issue as well as check other ways to solve it (in case you don't want to rely on reflection): https://blog.monosoul.dev/2022/09/16/spring-data-mongodb-polymorphic-fields/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With