Configuration

After some feedback about some unexpected use cases, this transition has been postponed and the requisite MapperOptions based methods undeprecated. Programmatic configuration is a much more popular, and in some cases vital, option than previously known. So for now, those methods will stay while the future of configuration and mapping gets a rethink. Apologies for the noise and dust.

Starting in Morphia 3.0, configuration will be driven via a configuration file rather than programmatic configuration. The file should live in your build’s resources folder and be named META-INF/morphia-config.properties. This is being done for a number of reasons:

  1. Better align with usage in frameworks such as Quarkus and Spring Boot

  2. Allow for deployment specific variations based on the deployment environment.

  3. Allow for certain optimizations and simplifications because we know more about the runtime set up earlier in the process.

The configuration mechanism, based on Microprofile Config, allows for a number of variations which were awkward, at best, and usually downright impossible when using the traditional programmatic configuration. Reference documentation for the configuration file can be found in the javadoc but we’ll go in depth on a few items here. An example configuration file is included below. All possible configuration elements are listed. However, if the default values are acceptable that element can be safely omitted from your file.

Morphia.createDatastore() can be called with and without an explicit MorphiaConfig argument. When not explicitly given a MorphiaConfig, Morphia will attempt to load the configuration file at the location show above. Failing to find a configuration file, it will create a new configuration using the default values which can be found in the javadoc. These values are unlikely to be suitable long term but should be sufficient for newcomers looking to test things out a little before committing to adopt Morphia.

Manually loading configurations

In some cases, you might find the need for multiple configuration files. Such scenarios include varying test environments/configurations, multiple dataset configurations, or externally supplied configurations. In such cases, you can manually load those configurations using MorphiaConfig.load(). You can then pass this instance to createDatastore().

Dynamic configuration creation

For many, the ability to dynamically create MorphiaConfig instances is not just a nicety but a hard requirement. In this case, there are methods on MorphiaConfig to return a new version of the configuration with the updated value. MorphiaConfig is immutable, however, so be sure to use the returned reference and not the original. It should also be noted that once a Datastore is created using a MorphiaConfig, that configuration is fixed and can not be changed. A new Datastore would need to be created with the updated version.

Collection and property naming

  • morphia.collection-naming

  • morphia.property-naming

Traditionally Morphia has used the class’s "simple name" for the collection name if you chose to not manually map the name of an entity’s collection. Similarly, an entity’s properties were named after the Java field name unless mapped otherwise with the @Property annotation. In 2.0, however, we defined some naming strategies. The naming strategies supported out of the box are:

  1. identity This is the legacy behavior Morphia has always used.

  2. lower This is simply the lower case form of the identity strategy.

  3. snake This transforms element names in to their snake case versions. For those coming from a Python background or who work with Python developers regularly, this should look familiar.

  4. camel This transforms element names in to their camel case versions. This is the form most java developers will be familiar with.

  5. kebab This transforms element names in to their kebab case versions. This looks exactly like the snake case but with - instead of _ so that it looks like it’s on a kebab skewer.

These strategies can all be accessed via the NamingStrategy class using their named methods. If you need a custom naming strategy, perhaps some hashing function to obscure element names, e.g., you can simply extend NamingStrategy yourself and implement whatever logic you might need.

For the provided strategies, you can simply use the shortened version as shown in the sample below. If you define your own strategy, you can enable it by giving the fully qualified classname (fqcn) instead. The same applies everywhere you see fqcn in the sample file.

Discriminator keys and values

  • morphia.discriminator

  • morphia.discriminator-key

Morphia has long hard coded the choice of how to encode an entity’s type in to the resulting documents in the database using the className key and the simple name of the class. The default is to use _t as the discriminator property key. This was chosen in part because of its use in other systems and also for its brevity. By default, Morphia 2.0 stores the entity type unless you configure your types otherwise.

Similar to how collections and fields have a naming strategy, we can apply a global function to determine the discriminator values should we choose. The choices here are simpler:

  1. className()/lowerClassName()

  2. simpleName()/lowerSimpleName()

Simple name is the fully qualified classname without the package name. These can all be accessed via their named methods on DiscriminatorFunction and just like the NamingStrategy cases, if the provided options are not sufficient, you can implement your own by subclassing DiscriminatorFunction and implementing your own function.

User-defined Codecs

  • morphia.codec-provider

Morphia makes heavy use of the Java driver’s Codec infrastructure. All the persistence of your entities is handled by Morphia-defined and -configured codecs. Morphia also makes use of driver-defined codecs with a select number of replacements more attuned to Morphia’s needs. This is typically sufficient for users' needs. However, there are invariably times when more control is needed.

Starting with 2.3, you can provide your own CodecProvider to customize how Morphia handles the types you’re interested in. It’s not advised to write custom codecs for your entities (why mark them as entities at that point?) but if you’re comfortable taking that on, then it is, of course, your prerogative. You can register your custom CodecProvider implementation using the property listed above.

For details on how to write a Codec and a CodecProvider, please consult the driver’s documentation. Using your custom CodecProvider, you can supply as many Codec implementations as you need.

In future versions, this will be updated to use the SPI mechanism. This will not only make it consistent with other configurable elements but will make it possible to supply more than one CodecProvider. See the note below about using the ServiceLoader with Java 9 modules.

Property Codecs

As unusual as is the need for a custom codec to handle types, there are rare cases where how Morphia processes a property on entity needs to be customized. This processing is handled via the MorphiaPropertyCodecProvider. Morphia discovers these custom implementations via SPI the details of which won’t be covered here.

Legacy Configuration

Morphia can be configured in one of two ways: the legacy mode and the modern mode. The defaults in the configuration code will give you the modern configuration and is the recommended way to configure Morphia. However, if you’re upgrading an older project, you very likely have more data than can be easily updated within any reasonable limits. For those, cases using the legacy configuration is rather straightforward.

Using the legacy configuration is a matter of defining a few entries in your configuration file. An example of that is listed below. If you already have a MorphiaConfig in hand but would like to update it to reflect the legacy style configuration, you can call .legacy() on that reference and use the resulting MorphiaConfig instance.

Sample config files

Complete with defaults
######
# default=false
######
morphia.apply-caps=false
######
# default=false
######
morphia.apply-document-validations=false
######
# default=false
######
morphia.apply-indexes=false
######
# default=true
######
morphia.auto-import-models=false
######
# Optional
######
morphia.codec-provider=
######
# default=camelCase
# possible values=camelCase, identity, kebabCase, lowerCase, snakeCase, fqcn
######
morphia.collection-naming=camelCase
######
# default=morphia
######
morphia.database=morphia
######
# default=utc
# possible values=utc, system_default
######
morphia.date-storage=utc
######
# default=simpleName
# possible values=className, lowerClassName, lowerSimpleName, simpleName, fqcn
######
morphia.discriminator=simpleName
######
# default=_t
######
morphia.discriminator-key=_t
######
# default=false
######
morphia.enable-polymorphic-queries=false
######
# default=false
######
morphia.ignore-finals=false
######
# default=.*
######
morphia.packages=.*
######
# default=fields
# possible values=fields, methods
######
morphia.property-discovery=fields
######
# default=identity
# possible values=camelCase, identity, kebabCase, lowerCase, snakeCase, fqcn
######
morphia.property-naming=identity
######
# default=dev.morphia.query.DefaultQueryFactory
######
morphia.query-factory=dev.morphia.query.DefaultQueryFactory
######
# default=false
######
morphia.store-empties=false
######
# default=false
######
morphia.store-nulls=false
######
# default=standard
# possible values=unspecified, standard, c_sharp_legacy, java_legacy, python_legacy
######
morphia.uuid-representation=standard

Minimal config file
######
# default=true
######
morphia.auto-import-models=false

Legacy config file
######
# default=true
######
morphia.auto-import-models=false
######
# default=camelCase
# possible values=camelCase, identity, kebabCase, lowerCase, snakeCase, fqcn
######
morphia.collection-naming=identity
######
# default=utc
# possible values=utc, system_default
######
morphia.date-storage=system_default
######
# default=simpleName
# possible values=className, lowerClassName, lowerSimpleName, simpleName, fqcn
######
morphia.discriminator=className
######
# default=_t
######
morphia.discriminator-key=className
######
# default=dev.morphia.query.DefaultQueryFactory
######
morphia.query-factory=dev.morphia.query.LegacyQueryFactory

The legacy query factory is deprecated and will be removed in 3.0. It is advisable to switch to the newer query filters based API now to avoid breakage.

Some notes on ServiceLoader

Morphia provides a number of extensibility points using the SPI mechanism available in the JVM. This allows for seamless, config-free inclusion of different functionality. In general, this works without notice because most users will not need to implement such features and so needn’t be bothered with such details. However, if you are one of the lucky ones that does need to know and you use Java modules, please be aware that the usual services file in META-INF/services won’t work. In order to export your service for Morphia to find you need an entry in your module-info.java file as shown below:

provides dev.morphia.mapping.codec.MorphiaPropertyCodecProvider with com.foo.MyCodecProvider;