{"id":22055833,"url":"https://github.com/axelspringer/polymorphia","last_synced_at":"2025-07-04T16:06:18.374Z","repository":{"id":48043732,"uuid":"108032804","full_name":"axelspringer/polymorphia","owner":"axelspringer","description":"A very fast POJO codec for MongoDB (used in conjunction with the Mongo Java Driver) that handles generic types as well as polymorphic class hierarchies","archived":false,"fork":false,"pushed_at":"2023-01-02T15:12:01.000Z","size":283,"stargazers_count":25,"open_issues_count":0,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-05-12T15:57:23.832Z","etag":null,"topics":["codec","entity","mapper","mongo","mongo-java-driver","mongodb","orm","pojo","pojo-codec"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/axelspringer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-23T20:00:06.000Z","updated_at":"2025-01-05T10:32:07.000Z","dependencies_parsed_at":"2023-02-01T03:16:01.704Z","dependency_job_id":null,"html_url":"https://github.com/axelspringer/polymorphia","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/axelspringer/polymorphia","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axelspringer%2Fpolymorphia","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axelspringer%2Fpolymorphia/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axelspringer%2Fpolymorphia/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axelspringer%2Fpolymorphia/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/axelspringer","download_url":"https://codeload.github.com/axelspringer/polymorphia/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/axelspringer%2Fpolymorphia/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263572855,"owners_count":23482533,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codec","entity","mapper","mongo","mongo-java-driver","mongodb","orm","pojo","pojo-codec"],"created_at":"2024-11-30T16:11:41.176Z","updated_at":"2025-07-04T16:06:18.347Z","avatar_url":"https://github.com/axelspringer.png","language":"Java","readme":"## POJO (Plain old java objects) Codec for mongo db\n\nPolymorphia is an easy to use object document mapper for the JVM. It transparently maps your POJOs (Java entities, domain objects) to MongoDB BSON (eventually to MongoDB documents). \nYou can use this codec to encode plain old java objects into a Mongo database and to decode those back again into POJOs with minimal configuration.\n\n## Main features\n * compatible with JDK 11 (compiled with JDK 8) \n * minimal configuration, easy to use - but configurable when needed\n * convention over configuration approach\n * use standard mongo-java-driver features to store and retrieve entities from MongoDB (including async and synchronous support)\n * support for polymorphic class hierarchies\n * support for generic types\n * works properly with any existing [Codec](org.bson.codecs.Codec) or [TypeCodec](src/main/java/de/bild/codec/TypeCodec.java) (if you need support for generic types e.g.)\n * fine grained control over discriminator keys and values (needed to morph into the correct POJO while decoding)\n * allows for easy evolution of domain model (renaming of classes or evolution of non-polymorphic structures into polymorphic structures e.g.)\n * fast\n * support for primitives and their object counterparts, sets, lists, maps (Map\u003cString, T\u003e as well as Map\u003cKeyType,ValueType\u003e) multi dimensional arrays, enums\n * allows for easy **application** [@Id(collectible = true)](src/main/java/de/bild/codec/annotations/Id.java) generation [@see CollectibleCodec](org.bson.codecs.CollectibleCodec)\n   * example: [IdTest](src/test/java/de/bild/codec/id/IdTest.java)\n   * set **collectible = false** to let MongoDB generate an ObjectId\n * provides fine grained control over restructuring data written to mongo (and reading from mongo) \n * partial POJO codec [SpecialFieldsMapCodec](src/main/java/de/bild/codec/SpecialFieldsMapCodec.java)\n * life cycle hook support (pre safe, post load)\n * support mongo java driver \u003e version 3.5\n * resilient (control how to decode broken entities within your MongoDB)\n * global and/or annotation based configuration for null handling while encoding / undefined handling while decoding\n\n\n## Release Notes\n\nRelease notes are available [release_notes.md](release_notes.md).\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ede.bild.backend\u003c/groupId\u003e\n    \u003cartifactId\u003epolymorphia\u003c/artifactId\u003e\n    \u003cversion\u003e2.8.2\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nMake sure you include a suitable mongo-java-driver version in your project.\n\n## Usage\nNote: There are plenty of code examples to be found in the tests.\n\nAttention: In order **to scan** packages for pojo domain model classes you need to provide either [spring-core](https://github.com/spring-projects/spring-framework) library or [org.reflections.reflections](https://github.com/ronmamo/reflections) library in the class path.\n**Alternatively you could register all your POJO classes one by one!**\nOr when using Polymorphia \u003e version 2.3.0 you can register your own [ClassResolver](src/main/java/de/bild/codec/ClassResolver.java) to provide a list of your domain model classes.\n\nInstantiate the [PojoCodecProvider](src/main/java/de/bild/codec/PojoCodecProvider.java) and add it to the CodecRegistry.\nExample: [PolymorphicReflectionCodecTest](src/test/java/de/bild/codec/PolymorphicReflectionCodecTest.java)\n\nOne motivation to start this project was the missing feature of existing Pojo-codec solutions to easily model polymorphic structures and write/read those to/from the database (e.g.[https://github.com/mongodb/morphia/issues/22](https://github.com/mongodb/morphia/issues/22))\n\nSimple example @see example [PolymorphicCollectionTest](src/test/java/de/bild/codec/PolymorphicCollectionTest.java)\n```java\n        public interface MyCollectionThing {}\n    \n        public interface Shape extends MyCollectionThing {}\n    \n        public class Circle implements Shape {\n            int radius;\n        }\n    \n        public class Square implements Shape  {\n            int x;\n        }\n\n        /** Non-shapy things ... **/\n        static class Animal implements MyCollectionThing {\n            String name;\n        }\n        static class Mammal extends Animal{\n    \n        }\n        static class Cat extends Mammal {\n            int age;\n        }\n        static class Dog extends Mammal {\n            String color;\n        }\n        static class Tuna extends Fish {\n            int size;\n        }\n                        \n        public static void main() {\n            MongoCollection\u003cMyCollectionThing\u003e collection = database.getCollection(\"things\").withDocumentClass(MyCollectionThing.class);\n    \n            Dog brownDog = new Dog(\"brown\");\n            Tuna oldTuna = new Tuna(123);\n            Circle smallCircle = new Circle(2);\n            Cat generalCat = new Cat(9);\n            Rectangle rectangle = new Rectangle(2, 5);\n            Circle mediumCircle = new Circle(5);\n            Square square = new Square(4);\n        \n            // insert all items into collection of type MongoCollection\u003cMyCollectionThing\u003e\n            collection.insertMany(Arrays.asList(brownDog, oldTuna, smallCircle, generalCat, rectangle, mediumCircle, square));\n            \n            // only find things in collection of type Shape.class...\n            for (MyCollectionThing myCollectionThing : collection.find(POJO_CODEC_PROVIDER.getTypeFilter(Shape.class, codecRegistry))) {\n                System.out.println(myCollectionThing.getClass());\n            }\n        \n            //would print---\n            //Circle\n            //Rectangle\n            //Circle\n            //Square\n\n        }\n        \n```\n\nMore complex example (including use of generics)\n```java\npublic class BasePojo\u003cT\u003e {\n    T someValue;\n}\n\npublic class Pojo extends BasePojo\u003cInteger\u003e {\n    @Id(collectible = true)\n    ObjectId id;\n    \n    String aString;\n    int anInt;\n    List\u003cMap\u003cLong, Date\u003e\u003e complexList; // some complex things\n    float[][][] floats; // multi dimensional float array\n}\n```\n#Setup\n\nMinimal working example can be found in the tests: See [Tutorial](src/test/java/de/bild/backend/polymorphia/tutorial/Tutorial.java)   \nSpring Boot Example Setup: See [MongoClientConfiguration](src/test/java/de/bild/backend/polymorphia/MongoClientConfiguration.java)\n\n```java\n    PojoCodecProvider.builder()\n        .register(\"de.bild.codec.model\") //adds all static classes of a package to the model\n        .register(Pojo.class) // adds a single class\n        .build();\n\n    CodecRegistry pojoCodecRegistry = fromRegistries(fromProviders(pojoCodecProvider), MongoClient.getDefaultCodecRegistry());\n    \n    MongoClient mongoClient = // either use spring auto configuration or create your own MongoClient instance with the above codecregistry\n    \n     // now simply write POJOs to the database or read from it\n    // many examples can be found in the tests\n    \n```\nIn general your declared POJOs will need an empty default constructor, or a no-arg constructor (access level private will do). (Please feel free to add a mechanism to allow for control over entity instantiation)\n\nIf you need to encode/decode enums, you could simply add [EnumCodecProvider](src/main/java/de/bild/codec/EnumCodecProvider.java) to the codec registry.\nIn that case enum will be encoded with their names. If you have special needs for enum serialization/deserialization, you can register your own enum codec and chain it with a higher priority. \n\n## Polymorphic hierarchies\n\nThe following code examples describe how you can control discriminator keys and discriminator values. These are needed to enable the codec to morph into the correct polymorphic classes.  \nPlease note that there is absolutely no need to override the default behaviour.  \nIf your class structures are not annotated with [@Discriminator](src/main/java/de/bild/codec/annotations/Discriminator.java) or [@DiscriminatorKey](src/main/java/de/bild/codec/annotations/DiscriminatorKey.java) the default behaviour is as follows:\n* [@Discriminator](src/main/java/de/bild/codec/annotations/Discriminator.java) is the simple class name\n* [@DiscriminatorKey](src/main/java/de/bild/codec/annotations/DiscriminatorKey.java) is \"_t\"\n* **ATTENTION: Discriminator-information is only persisted in the database, if polymorphic structures are detected**\n* to force persisting discriminator information to the database either add [@Polymorphic](src/main/java/de/bild/codec/annotations/Polymorphic.java) to your class structure or you annotate a class with [@Discriminator](src/main/java/de/bild/codec/annotations/Discriminator.java)\n\n```java\npublic interface Base {\n}\n\n// discriminator: _t : \"A\"\npublic class A implements Base {}\n\n// discriminator: _t : \"NewEncodingDiscriminatorValue\"\n// decoding also if _t : \"SomeAlias\" or _t : \"SomOtherOldValue\"\n@de.bild.codec.annotations.Discriminator(value = \"NewEncodingDiscriminatorValue\", aliases = {\"SomeAlias\", \"SomOtherOldValue\"})\npublic class B implements Base {}\n\n// use @de.bild.codec.annotations.Polymorphic to instruct the encoder to definitely write a discriminator into the database\n// otherwise no discriminator will be written, as the codec assumes this is a non-polymorphic POJO \n@de.bild.codec.annotations.Polymorphic\npublic class InitialPojoWithNoSubTypes {}\n\n// if later in yor project the POJO evolves into polymorphic structures, mark it as fallback to decode any entity within the database that has no discriinator\n@de.bild.codec.annotations.DiscriminatorFallback\npublic class InitialPojoWithNoSubTypes {}\n\n// you can control the discriminator key as follows\n// results in: \"someKey\" : \"PojoEntity\"\n// Attention: You can use different discriminator keys within your polymorphic structure, but this may be confusing,hence it is discouraged\n// If two or more entities in a polymorphic structure use the same combination of DiscriminatorKey:Discriminator an exception will be thrown when initializing the CodecProvider as then the destination Pojo is ambiguous\n@de.bild.codec.annotations.DiscriminatorKey(\"someKey\")\n@de.bild.codec.annotations.Discriminator(\"PojoEntity\")\npublic class Pojo {}\n```\n\n\nIf you try to encode an entity that is not registered during setup of the POJO codec, but which is a subclass of a class that was registered during setup phase,\nthe codec walks up the class hierarchy until a registered entity is found that is in the set of registered classes. Then the properties of __that__ class will be persisted.\nThat means you loose properties while encoding to the database. This behaviour might be desired, e.g. if your working instance contains information that is only needed at runtime but that are not relevant for storage.\nAlternatively you could mark those properties with [@Transient](src/main/java/de/bild/codec/annotations/Transient.java)\nNote that decoding into POJOs that are not registered within the codec does not work.  \nA [NonRegisteredModelClassException](src/main/java/de/bild/codec/NonRegisteredModelClassException.java) will be thrown.\n\n\n## Application Id generation\nThe mongo java driver allows for application id generation. In general to enable this feature, the mongo java driver requests the codec responsible for an entity to implement org.bson.codecs.CollectibleCodec. This is handled transparently within Polymorphia and you do not need to care about.   \n\nWithin your pojo class you can annotate a **single** property with [@Id(collectible=true, value = CustomIdGenerator.class)](src/main/java/de/bild/codec/annotations/Id.java)\nWhen the codec for this pojo is being built and such an Id annotated field is found, the final codec will be wrapped into a java.lang.reflect.Proxy that implements the org.bson.codecs.CollectibleCodec interface.\nIf your Pojo is part of a polymorphic structure, one or all of your subclasses within that structure can allow for application id generation. Each subclass can generate individual Ids of different types. The mongo database is capable of handling different ids in one collection.\nThe PolymorphicReflectionCodec takes care of this and will implement the CollectibleCodec interface if (and only if) at least one PolymorphicCodec (one for each sub type) is found to be collectible. \n\n\n## Updating entities\nUpdating entities is straight forward.\n\n```java\n    Bson update = combine(set(\"user\", \"Jim\"),  / String\n                set(\"action\", Action.DELETE), // enum\n                currentDate(\"creationDate\"),\n                currentTimestamp(\"_id\"));\n    FindOneAndUpdateOptions findOptions = new FindOneAndUpdateOptions();\n        findOptions.upsert(true);\n        findOptions.returnDocument(ReturnDocument.AFTER);\n\n        MongoCollection\u003cPojo\u003e pojoMongoCollection = mongoClient.getDatabase(\"test\").getCollection(\"documents\").withDocumentClass(Pojo.class);\n\n        Pojo pojo = pojoMongoCollection.findOneAndUpdate(Filters.and(Filters.lt(DBCollection.ID_FIELD_NAME, 0),\n                Filters.gt(DBCollection.ID_FIELD_NAME, 0)), update, findOptions);\n\n```\n\n__Attention__\nPlease be aware that you __cannot__ directly update fields with generic types! You would need to create a concrete subclass of that type so the POJO codec can determine runtime type information, as otherwise type arguments cannot be resolved and you would loose information while encoding to the database (best case).\nPlease have a look at [UpdateTest](src/test/java/de/bild/codec/update/UpdateTest.java) for an example on how to do that.\nPlease feel free to support this project and provide a solution for that issues. Ideas on how to solve it are outlined in the test itself.\n\n## Ignoring classes during package scan\n\nWhen builing your PojoCodecProvider, register some arbitrary annotation types that - once found at your model classes - have the effect that these classes won't be indexed.\nThe advantage is, that you can keep all your model classes in one package even though you register different Codecs for some types. \nMark the types you want to be ignored with one (or if desired more) of your created annotation types.\nYou may wonder, why different annotations can be defined to ignore types. As ou could define multiple instances of PojoCodecProvider you have better control of type exclusions, depending on the instantiated PojoCodecProvider. \n \n```java\n    PojoCodecProvider.builder()\n        .register(Pojo.class.getPackage().getName())\n        .ignoreTypesAnnotatedWith(IgnoreAnnotation.class)\n        .build()\n\n```\n\n## Advanced usage\n\nIf you have a data structure within your mongo whereby you are not exactly sure what fields are declared, but you know \nsome of the declared fields, use the following approach to decode the known fields into known POJOs.\n@Todo: It would be great, if the names of the properties could be inferred from the method names.\n\n```java\npublic class MapWithSpecialFieldsPojo extends Document implements SpecialFieldsMap {\n    @FieldMapping(\"someNiceDate\")\n    public Date getSomeNiceDate() {\n        return (Date) get(\"someNiceDate\");\n    }\n    @FieldMapping(\"someNicePojo\")\n    public SomeNicePojo getSomeNicePojo() {\n        return (SomeNicePojo) get(\"someNicePojo\");\n    }\n}\n```\n\n\n## Hooks for custom codecs\n\nIf you want to provide your own serialization and deserialization codecs but at the same time want to benefit from Polymorphias capabilities \nto map polymorphic structures, you can chain your custom codecs by registering a [CodecResolver](src/main/java/de/bild/codec/CodecResolver.java) when building \nthe [PojoCodecProvider](src/main/java/de/bild/codec/PojoCodecProvider.java)  \nThese codecs however need to implement a specialization of org.bson.codecs.Codec namely [PolymorphicCodec](src/main/java/de/bild/codec/PolymorphicCodec.java)   \nThis interface adds the following features:\n * handles discriminator issues like check for fields names that must not be equal to any discriminator key\n * provides methods to encode any additional fields besides the discriminator  \n * copies the method signatures of org.bson.codecs.CollectibleCodec but without implementing CollectibleCodec (The reason is explained in the section [Application Id generation])\n   * this enables your codec to generate application ids\n * provides methods to instantiate your entities and set default values\n\nFor an example of usage @see [CodecResolverTest](src/test/java/de/bild/codec/CodecResolverTest.java)\n\n```java\nPojoCodecProvider.builder()\n    .register(CodecResolverTest.class)\n    .registerCodecResolver((CodecResolver) (type, typeCodecRegistry) -\u003e {\n        if (TypeUtils.isAssignable(type, Base.class)) {\n            return new DocumentCodec(type, typeCodecRegistry);\n        }\n        return null;\n    })\n    .build()\n```\n\n### [TypeCodecProvider](src/main/java/de/bild/codec/TypeCodecProvider.java)\nIf you desire to register a codec provider that can provide a codec for any given type (not just for java.lang.Class) you may register a [TypeCodecProvider](src/main/java/de/bild/codec/TypeCodecProvider.java)   \nFor an example of usage have a look at [TypeCodecProviderTest](src/test/java/de/bild/codec/typecodecprovider/TypeCodecProviderTest.java)\nYou can override any fully specified type or generic type. You can e.g. register alternative codecs for Set or List or Map.   \nIn contrast to org.bson.codecs.configuration.CodecProvider the registered [TypeCodecProvider](src/main/java/de/bild/codec/TypeCodecProvider.java) accepts any java.lang.reflect.Type\nPlease be aware of the fact, that these TypeCodecProviders will only take effect when resolved within PojoCodecProvider. \n\n\n## Default values\nAs of Polymoprhia version 2.0.0 the developer has better control over null-handling while encoding to the database. Additionally a developer can control default values when decoding undefined fields.\nUse [EncodeNulls](de.bild.codec.annotations.EncodeNulls) to decide whether you need nulls written to the database.     \nYou can convert null values into defaults before the encoder kicks in. Use [EncodeNullHandlingStrategy](de.bild.codec.annotations.EncodeNullHandlingStrategy) to assign values to null fields if desired.\n\n\nWhile decoding fields that are present in the pojo but have undefined values in the database (evolution of pojos!) you can assign a [DecodeUndefinedHandlingStrategy](de.bild.codec.annotations.DecodeUndefinedHandlingStrategy).\n\nThe mentioned annotations can be used at class level and field level. Field level annotations overrule class annotations. It is also possible to set global behaviour for these configurations. When building your [PojoCodecProvider](src/main/java/de/bild/codec/PojoCodecProvider.java) use the Builder methods to control the global defaults.\n* de.bild.codec.PojoCodecProvider.Builder#encodeNullHandlingStrategy(Strategy)  -\u003e defaults to CODEC (historical reasons)\n* de.bild.codec.PojoCodecProvider.Builder#decodeUndefinedHandlingStrategy(Strategy) -\u003e defaults to KEEP_POJO_DEFAULT\n* de.bild.codec.PojoCodecProvider.Builder#encodeNulls(boolean) -\u003e defaluts to false\n\nHave a look at [NullHandlingTest](src/test/java/de/bild/codec/NullHandlingTest.java) for an example.\n\n```java\n\nPojoCodecProvider.builder()\n        .register(NullHandlingTest.class)\n        .encodeNulls(false)\n        .decodeUndefinedHandlingStrategy(DecodeUndefinedHandlingStrategy.Strategy.KEEP_POJO_DEFAULT)\n        .encodeNullHandlingStrategy(EncodeNullHandlingStrategy.Strategy.KEEP_NULL)\n        .build()\n                                    \n\n    @EncodeNulls(false) \n    public class Pojo {\n        @DecodeUndefinedHandlingStrategy(DecodeUndefinedHandlingStrategy.Strategy.CODEC)\n        Integer aField;\n  \n        @DecodeUndefinedHandlingStrategy(DecodeUndefinedHandlingStrategy.Strategy.SET_TO_NULL)\n        @EncodeNulls         \n        String anotherField;\n    }\n```\n\n## Resilience\nIf you perform a findMany() operation, it may happen, that some items in the resulting result set may incur exceptions due to mismatching data types e.g.    \nWith [@DecodingPojoFailureStrategy](src/test/java/de/bild/codec/annotations/DecodingPojoFailureStrategy.java) and [@DecodingFieldFailureStrategy](src/main/java/de/bild/codec/annotations/DecodingFieldFailureStrategy.java) \nit is possible to control exceptional states while decoding entities.\n\n## Build\n\nTo build and test the driver:\n\n```\n$ git clone https://github.com/axelspringer/polymorphia\n$ cd polymorphia\n$ mvn clean install\n```\nThe tests will spin up a spring boot environment and use [flapdoodle](https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo) to run an in-memory mongo db.\n\n\n## Todos\n* Once Mongo Java Driver 3.5 (or 3.6 most likely) is released, the codec needs to add some lines of code to be more fault tolerant.\n[JAVA-2416](https://jira.mongodb.org/browse/JAVA-2416). If multiple marks can be set on the reader we can set a mark whenever \nwe read a new entity from the reader. If that entity contains malformed data that cannot be decoded an exception may result and the reader is left in an arbitrary state. \nResetting the reader to the last mark and skipping the broken entity solves this problem.\n* adding more tests\n* implement the todos/features mentioned above\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faxelspringer%2Fpolymorphia","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faxelspringer%2Fpolymorphia","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faxelspringer%2Fpolymorphia/lists"}