Working with the MongoDB Template API in Spring

MongoTemplate and its reactive counterpart live in the org.springframework.data.mongodb.core package, serving as the core component of Spring's MongoDB integration. It provides a comprehensive feature set for database interactions, including convenient CRUD operations for MongoDB documents and built-in mapping between domain objects and database documents. After proper configuration, MongoTemplate is thread-safe and can be reused across multiple application components.

Convenient Operation Methods

MongoTemplate implements the MongoOperations interface. To keep the API familiar for developers already comfortable with the native MongoDB driver, most method names on MongoOperations match those available on the driver's Collection object. You will find common methods like find, findAndModify, findAndReplace, findOne, insert, remove, save, update, and updateMulti, mirroring the native driver naming. The design goal is to make transitioning between the native driver and MongoOperations as seamless as possible.

The key difference between the two APIs is that MongoOperations accepts domain objects as input instead of raw Document instances. Additionally, MongoOperations provides a fluent API for building Query, Criteria, and Update objects, instead of requiring you to manually populate Document instances for operation parameters. The preferred way to reference MongoTemplate operations is through the MongoOperations interface.

Execute Callbacks

While MongoTemplate provides many convenience methods for common tasks, you can use execute callbacks if you need direct access to the native MongoDB driver API. These callbacks give you direct references to the native MongoCollection or MongoDatabase objects for low-level access.

Available callback method signatures:

  • <T> T execute(Class<?> entityClass, CollectionCallback<T> action): Runs the provided CollectionCallback against the collection mapped to the given entity class
  • <T> T execute(String collectionName, CollectionCallback<T> action): Runs the CollectionCallback on the collection with the given name
  • <T> T execute(DbCallback<T> action): Runs the DbCallback and handles automatic exception translation
  • <T> T execute(String collectionName, DbCallback<T> action): Runs the DbCallback on the specified collection with automatic exception translation
  • <T> T executeInSession(DbCallback<T> action): Runs the DbCallback on a single dedicated connection to ensure consistency for write-heavy workloads where you need to read your own writes

The following example uses a CollectionCallback to check for the existence of a specific index:

boolean indexExists = template.execute("geolocation", collection ->
    Streamable.of(collection.listIndexes(org.bson.Document.class))
        .stream()
        .map(doc -> doc.get("name"))
        .anyMatch("location_2d"::equals)
);

Fluent API

As the core low-level interaction component for MongoDB in Spring, MongoTemplate includes a wide range of method overloads covering everything from collection creation, index management, and CRUD operations to advanced features like map-reduce and aggregation. Most overloads exist to handle optional or nullable parameters.

FluentMongoOperations provides a narrower, more readable fluent interface built on top of MongoOperations. Entry points such as insert(…), find(…), update(…) follow a natural naming convention based on the operation type. After the entry point, the API only exposes context-relevant methods that lead to a terminal method that executes the operation against the database, as shown below:

List<Jedi> allJedi = template.query(SWCharacter.class)
  .inCollection("star-wars")
  .as(Jedi.class)
  .matching(query(where("jedi").is(true)))
  .all();
  1. Base type used for field mapping during query parsing
  2. Explicit collection name, used when not defined on the domain type
  3. Target result type, used when different from the base domain type
  4. Filter criteria for the query

Projections allow MongoTemplate to optimize result mapping by only fetching the fields required by the target projection type. This works when the query has no explicit field restrictions and the target type is a closed interface or DTO projection. Note that projections cannot be applied to DBRefs.

You can switch between retrieving single or multiple results via different terminal methods, with support for List and Stream return types: first(), one(), all(), and stream().

When writing geospatial queries with near(NearQuery), the set of available terminal methods is adjusted to only return valid output compatible with MongoDB's geoNear command, as shown below:

GeoResults<Jedi> jediNearAlderaan = template.query(SWCharacter.class)
  .as(Jedi.class)
  .near(alderaan) // NearQuery.near(-73.9667, 40.78).maxDis…
  .all();

Exception Translation

Spring provides consistent exception translation across a wide range of data access technologies. Spring's MongoDB support extends this capability by implementing the PersistenceExceptionTranslator interface, which converts native MongoDB driver exceptions into Spring's consistent data access exception hierarchy.

The main benefit of this mapping is that you can write portable exception handling code without hardcoding MongoDB-specific error codes. All Spring data access exceptions inherit from the root DataAccessException class, so you can catch all database-related exceptions in a single try-catch block. Note that not all exceptions thrown by the MongoDB driver inherit from MongoException, but all original exception details and messages are preserved, so no information is lost.

Example mappings include: driver network exceptions are mapped to DataAccessResourceFailureException, and MongoDB error codes 1003, 12001, 12010, 12011, and 12012 are mapped to InvalidDataAccessApiUsageException. See the MongoExceptionTranslator implementation for the full set of mappings.

Domain Type Mapping

Mapping between MongoDB documents and domain classes is delegated to implementations of the MongoConverter interface. Spring provides MappingMongoConverter as the default implementation, and you can implement a custom converter if needed. MappingMongoConverter supports explicit mapping via annotation metadata, and also uses convention over configuration for ID and collection name mapping to convert unannotated domain objects automatically. These conventions and annotation usage are covered in detail in the separate guide on object mapping for Spring Data MongoDB.

Configuration

You can register a MongoTemplate instance via Java or XML configuration, as shown below. The following Java configuration registers a MongoClient and enables Spring's exception translation:

@Configuration
class AppConfig {

  @Bean
  MongoClient mongoClient() {
      return MongoClients.create("mongodb://localhost:27017");
  }

  @Bean
  MongoOperations mongoTemplate(MongoClient mongoClient) {
      return new MongoTemplate(mongoClient, "geospatial");
  }
}

XML-based bean registration:

<mongo:mongo-client host="localhost" port="27017" />

<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
  <constructor-arg ref="mongoClient" />
  <constructor-arg name="databaseName" value="geospatial" />
</bean>

MongoTemplate (and its reactive counterpart ReactiveMongoTemplate) provides several overloaded constructors:

  • MongoTemplate(MongoClient mongo, String databaseName): Creates a template with the given MongoClient and default target database
  • MongoTemplate(MongoDatabaseFactory mongoDbFactory): Accepts a MongoDatabaseFactory that encapsulates the MongoClient, database name, and authentication credentials
  • MongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter): Adds a custom MongoConverter for domain-document mapping

Additional optional properties you can configure on the template include default WriteResultCheckingPolicy, WriteConcern, ReadPreference, and other settings covered below.

Default Read Preference

The default read preference is used for all read operations when no custom preference is specified for an individual query.

WriteResultChecking Policy

During development, its useful to automatically log or throw an exception when a MongoDB write operation returns an error. Without this setting, operations can appear to succeed even when no changes are made to the database. You can set the WriteResultChecking property to one of two values: EXCEPTION to throw an exception on error, or NONE to take no action. The default value is NONE.

Default WriteConcern

You can set a default WriteConcern for all write operations if it is not already configured at the driver level. If this property is not set, MongoTemplate defaults to the WriteConcern configured on the native driver's MongoClient or Collection.

WriteConcernResolver

For advanced use cases where you need different WriteConcern values per operation (for insert, update, remove, and save operations), you can configure a custom WriteConcernResolver strategy on MongoTemplate. Since MongoTemplate persists POJOs, this interface lets you build a custom strategy that maps specific domain classes to different WriteConcern values. The interface definition is:

public interface WriteConcernResolver {
  WriteConcern resolve(MongoAction action);
}

You can use the MongoAction parameter to determine the correct WriteConcern and fall back to the template's default value when needed. MongoAction provides access to the target collection name, domain class, converted Document, operation type (REMOVE, UPDATE, INSERT, INSERT_LIST, SAVE), and other context. The following example shows a custom resolver that applies different WriteConcern settings to two groups of entity types:

public class CustomWriteConcernResolver implements WriteConcernResolver {

  @Override
  public WriteConcern resolve(MongoAction action) {
    String simpleName = action.getEntityType().getSimpleName();
    if (simpleName.contains("Audit")) {
      return WriteConcern.ACKNOWLEDGED;
    } else if (simpleName.contains("Metadata")) {
      return WriteConcern.JOURNALED;
    }
    return action.getDefaultWriteConcern();
  }
}

Disable Entity Lifecycle Events

MongoTemplate publishes entity lifecycle events by default when modifying data. You can disable this feature if you have no registered event listeners to reduce overhead:

@Bean
MongoOperations mongoTemplate(MongoClient mongoClient) {
    MongoTemplate template = new MongoTemplate(mongoClient, "geospatial");
    template.setEntityLifecycleEventsEnabled(false);
    return template;
}

Configure EntityCallbacks

After lifecycle events are fired, MongoTemplate invokes EntityCallbacks. If auto-configuration does not set this up for your application, you can configure it explicitly via the template API:

@Bean
MongoOperations mongoTemplate(MongoClient mongoClient) {
    MongoTemplate template = new MongoTemplate(mongoClient, "your-db");
    template.setEntityCallbacks(EntityCallbacks.create(...));
    return template;
}

Document Count Configuration

Setting MongoTemplate#useEstimatedCount(…​) to true will route count operations with empty filters to MongoDB's estimated count operation, as long as there is no active transaction and the template is not bound to an existing client session.

Index and Collection Management

MongoTemplate and ReactiveMongoTemplate provide methods to manage collections and indexes, exposed through the IndexOperations helper interface (ReactiveIndexOperations for reactive applications). You can access these operations by calling indexOps() with either the collection name or the domain class (the collection name is derived from the class via conventions or annotation metadata). The IndexOperations interface is defined as:

public interface IndexOperations {

    String ensureIndex(IndexDefinition indexDefinition);

    void alterIndex(String name, IndexOptions options);

    void dropIndex(String name);

    void dropAllIndexes();

    List<IndexInfo> getIndexInfo();
}

Creating Indexes

You can create indexes on a collection to improve query performance, as shown in the following example:

template.indexOps(User.class)
    .ensureIndex(new Index().on("fullName",Order.ASCENDING));

The ensureIndex method guarantees that the defined index exists on the collection. You can create standard, geospatial, and text indexes using the IndexDefinition, GeospatialIndex, and TextIndexDefinition classes respectively. The following example shows how to create a geospatial index for a domain class:

@Document(collection="venues")
public class LocationPoint {

  @Id
  private String id;
  private String venueName;
  private double[] coordinates;

  @PersistenceConstructor
  LocationPoint(String venueName, double[] coordinates) {
    this.venueName = venueName;
    this.coordinates = coordinates;
  }

  public LocationPoint(String venueName, double x, double y) {
    this.venueName = venueName;
    this.coordinates = new double[] { x, y };
  }

  public String getVenueName() { return venueName; }
  public double[] getCoordinates() { return coordinates; }
}

template.indexOps(LocationPoint.class)
    .ensureIndex(new GeospatialIndex("coordinates"));

Both Index and GeospatialIndex support collation configuration.

Retrieving Index Information

The getIndexInfo method returns a list of IndexInfo objects containing metadata for all indexes defined on the collection, as shown below:

template.indexOps(User.class)
    .ensureIndex(new Index().on("age", Order.DESCENDING).unique());

List<IndexInfo> userIndexes = template.indexOps(User.class)
   .getIndexInfo();

Collection Management Operations

The following example checks for and creates a new custom collection:

MongoCollection<Document> customCollection = null;
if (!template.getCollectionNames().contains("MyCustomCollection")) {
    customCollection = template.createCollection("MyCustomCollection");
}

Collection creation supports custom configuration via CollectionOptions, including collations. Available collection management methods:

  • getCollectionNames: Returns the full set of collection names in the default database
  • collectionExists: Checks if a collection with the given name exists
  • createCollection: Creates a new uncapped collection
  • dropCollection: Deletes an existing collection
  • getCollection: Returns the native MongoCollection for the given name, creating it if it does not exist

Time Series Collections

MongoDB 5.0 introduced time series collections, optimized for efficient storage of time-ordered data like metrics and events. These collections must be created before inserting any data. The examples below show three different ways to create a time series collection:

Create via direct native driver access:

template.execute(db -> {
    CreateCollectionOptions options = new CreateCollectionOptions();
    options.timeSeriesOptions(new TimeSeriesOptions("timestamp"));

    db.createCollection("weather_readings", options);
    return "OK";
});

Create via CollectionOptions:

template.createCollection("weather_readings", CollectionOptions.timeSeries("timestamp"));

Create from annotation metadata on a domain class:

@TimeSeries(collection="weather_readings", timeField = "timestamp")
public class TemperatureMeasurement {

    String id;
    Instant timestamp;
    // additional fields
}

template.createCollection(TemperatureMeasurement.class);

This pattern can be transferred directly to the reactive API which exposes the same method signatures, just make sure to properly subscribe to the returned publishers.

Counting Documents

The template API provides multiple methods to count documents matching a given set of criteria, as shown in this example:

long resultCount = template.query(User.class)
    .matching(query(where("firstName").is("luke")))
    .count();

Before Spring Data MongoDB 3.x, count operations relied on MongoDB's internal collection statistics to get fast results. After the introduction of transactions in MongoDB, these statistics do not reflect in-transaction changes, requiring an aggregation-based approach for accurate counts. In version 2.x, MongoOperations.count() used collection statistics when there was no active transaction, and fell back to aggregation when a transaction was active.

Starting from Spring Data MongoDB 3.x, all count operations use the driver's countDocuments method with aggregation-based counting, regardless of whether a transaction is active. If your appliaction can tolerate the limitations of collection statistics, MongoOperations.estimatedCount() provides a faster alternative. Setting MongoTemplate#useEstimatedCount(…​) to true automatically routes empty-filter count operations to estimated count when there is no active transaction and the template is not bound to a session. You can still get an exact count via MongoTemplate#exactCount() when needed.

It should be noted that MongoDB's native countDocuments method and $match aggregation do not support $near and $nearSphere, requiring $geoWithin with $center/$centerSphere instead, and do not support $minDistance. Spring Data MongoDB automatically rewrites geospatial count queries to work around these limitations, as shown below:

{ location : { $near : [-73.99171, 40.738868], $maxDistance : 1.1 } }    // 1
{ location : { $geoWithin : { $center: [ [-73.99171, 40.738868], 1.1] } } }  // 2

{ location : { $near : [-73.99171, 40.738868], $minDistance : 0.1, $maxDistance : 1.1 } }    // 3
{$and :[ { $nor :[ { location :{ $geoWithin :{ $center :[ [-73.99171, 40.738868 ], 0.01] } } } ]}, { location :{ $geoWithin :{ $center :[ [-73.99171, 40.738868 ], 1.1] } } } ] }    // 4
  1. Original count query using $near
  2. Rewritten query using $geoWithin and $center
  3. Original count query with $near, $minDistance, and $maxDistance
  4. Rewritten query combining $nor and $geoWithin conditions to work around the lack of $minDistance support

Tags: Spring mongodb MongoTemplate Spring Data MongoDB

Posted on Tue, 23 Jun 2026 16:58:55 +0000 by sam_h