Overview
This guide shows you how to integrate MongoDB Client-Side Field Level Encryption (CSFLE) with Spring Data MongoDB.
Note
If you haven't worked with CSFLE before, see the CSFLE Tutorials guide in the MongoDB Server manual.
If you haven't worked with Spring Data MongoDB before, see the Spring Data MongoDB documentation.
GitHub Repository
To view the source code for this guide, see the mongodb-java-spring-boot-csfle repository on GitHub.
You can also retrieve the source code from its GitHub repository by running the following command:
git clone git@github.com:mongodb-developer/mongodb-java-spring-boot-csfle.git
Prerequisites
Before you use this guide, perform the following steps:
Install Java 17
Install the MongoDB Automatic Encryption Shared Library v7.0.2 or later
Review the README.md for information on how to run the example code.
You also need access to a MongoDB cluster running MongoDB v7.0.2 or later.
Example Features
This example provides templated code for a production environment, and includes the following features:
Multiple encrypted collections
Automated JSON Schema generation
Server-side JSON Schema
Separated clusters for Data Encryption Keys (DEKs) and encrypted collections
Automated DEK generation or retrieval
Spring Expression Language (SpEL) evaluation extension
Auto-implemented repositories
OpenAPI 3.0.1 documentation
The template follows SOLID principles to increase code readability and reuse. To learn more about SOLID, see SOLID on Wikipedia.
Project Diagrams
The following diagram shows the components required to create a CSFLE-enabled MongoClient that can encrypt and decrypt fields automatically. The arrows in the diagram show dependencies and relationships between components.

The arrows in the diagram show dependencies and relationships between components.
After the application establishes a connection with MongoDB, it uses a three-tier architecture to expose a REST API and manage communication with the MongoDB database, as shown in the following diagram:

Create the Key Vault Collection
This section shows you how to create the key vault collection and its unique index.
If you are starting from a new MongoDB cluster, you must first create the key vault collection and its unique index on the keyAltNames field.
Set Up the Key Vault and DEKs
The KeyVaultAndDekSetup class creates the key vault collection and its unique index by using a standard connection to MongoDB. Then it creates the DEKs required to encrypt the documents in each encrypted collection.
The following code shows the KeyVaultAndDekSetup class:
public class KeyVaultAndDekSetup { private static final Logger LOGGER = LoggerFactory.getLogger(KeyVaultAndDekSetup.class); private final KeyVaultService keyVaultService; private final DataEncryptionKeyService dataEncryptionKeyService; private String CONNECTION_STR; public KeyVaultAndDekSetup(KeyVaultService keyVaultService, DataEncryptionKeyService dataEncryptionKeyService) { this.keyVaultService = keyVaultService; this.dataEncryptionKeyService = dataEncryptionKeyService; } public void postConstruct() { LOGGER.info("=> Start Encryption Setup."); LOGGER.debug("=> MongoDB Connection String: {}", CONNECTION_STR); MongoClientSettings mcs = MongoClientSettings.builder() .applyConnectionString(new ConnectionString(CONNECTION_STR)) .build(); try (MongoClient client = MongoClients.create(mcs)) { LOGGER.info("=> Created the MongoClient instance for the encryption setup."); LOGGER.info("=> Creating the encryption key vault collection."); keyVaultService.setupKeyVaultCollection(client); LOGGER.info("=> Creating the Data Encryption Keys."); EncryptedCollectionsConfiguration.encryptedEntities.forEach(dataEncryptionKeyService::createOrRetrieveDEK); LOGGER.info("=> Encryption Setup completed."); } catch (Exception e) { LOGGER.error("=> Encryption Setup failed: {}", e.getMessage(), e); } } }
Important
In production, you can create the key vault collection and its unique index on the keyAltNames field manually. After you create them, you can remove this code.
This code uses a standard (not CSFLE-enabled) and temporary MongoClient connection in a try-with-resources block to create a collection and an index in the MongoDB cluster.
Create the Key Vault Collection
The KeyVaultServiceImpl class creates the key vault collection and the keyAltNames unique index.
The following code shows the KeyVaultServiceImpl class:
public class KeyVaultServiceImpl implements KeyVaultService { private static final Logger LOGGER = LoggerFactory.getLogger(KeyVaultServiceImpl.class); private static final String INDEX_NAME = "uniqueKeyAltNames"; private String KEY_VAULT_DB; private String KEY_VAULT_COLL; public void setupKeyVaultCollection(MongoClient mongoClient) { LOGGER.info("=> Setup the key vault collection {}.{}", KEY_VAULT_DB, KEY_VAULT_COLL); MongoDatabase db = mongoClient.getDatabase(KEY_VAULT_DB); MongoCollection<Document> vault = db.getCollection(KEY_VAULT_COLL); boolean vaultExists = doesCollectionExist(db, KEY_VAULT_COLL); if (vaultExists) { LOGGER.info("=> Vault collection already exists."); if (!doesIndexExist(vault)) { LOGGER.info("=> Unique index created on the keyAltNames"); createKeyVaultIndex(vault); } } else { LOGGER.info("=> Creating a new vault collection & index on keyAltNames."); createKeyVaultIndex(vault); } } private void createKeyVaultIndex(MongoCollection<Document> vault) { Bson keyAltNamesExists = exists("keyAltNames"); IndexOptions indexOpts = new IndexOptions().name(INDEX_NAME) .partialFilterExpression(keyAltNamesExists) .unique(true); vault.createIndex(new BsonDocument("keyAltNames", new BsonInt32(1)), indexOpts); } private boolean doesCollectionExist(MongoDatabase db, String coll) { return db.listCollectionNames().into(new ArrayList<>()).stream().anyMatch(c -> c.equals(coll)); } private boolean doesIndexExist(MongoCollection<Document> coll) { return coll.listIndexes() .into(new ArrayList<>()) .stream() .map(i -> i.get("name")) .anyMatch(n -> n.equals(INDEX_NAME)); } }
After you create the key vault collection and index, you can close the standard MongoDB connection.
Create the Data Encryption Keys
This section shows you how to create the DEKs by using the ClientEncryption connection.
Configure the Key Vault Client
The MongoDBKeyVaultClientConfiguration class creates a ClientEncryption bean that the DataEncryptionKeyService uses to create the DEKs.
The following code shows the MongoDBKeyVaultClientConfiguration class:
public class MongoDBKeyVaultClientConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(MongoDBKeyVaultClientConfiguration.class); private final KmsService kmsService; private String CONNECTION_STR; private String KEY_VAULT_DB; private String KEY_VAULT_COLL; private MongoNamespace KEY_VAULT_NS; public MongoDBKeyVaultClientConfiguration(KmsService kmsService) { this.kmsService = kmsService; } public void postConstructor() { this.KEY_VAULT_NS = new MongoNamespace(KEY_VAULT_DB, KEY_VAULT_COLL); } /** * MongoDB Encryption Client that can manage Data Encryption Keys (DEKs). * * @return ClientEncryption MongoDB connection that can create or delete DEKs. */ public ClientEncryption clientEncryption() { LOGGER.info("=> Creating the MongoDB Key Vault Client."); MongoClientSettings mcs = MongoClientSettings.builder() .applyConnectionString(new ConnectionString(CONNECTION_STR)) .build(); ClientEncryptionSettings ces = ClientEncryptionSettings.builder() .keyVaultMongoClientSettings(mcs) .keyVaultNamespace(KEY_VAULT_NS.getFullName()) .kmsProviders(kmsService.getKmsProviders()) .build(); return ClientEncryptions.create(ces); } }
You can create a ClientEncryption bean by using the Key Management System (KMS) and use it to generate the DEKs. You need one DEK for each encrypted collection.
Implement the Data Encryption Key Service
The DataEncryptionKeyServiceImpl class creates and stores the DEKs. When you evaluate the SpEL expressions in the entities to create the JSON Schemas, you must retrieve the DEKs.
The following code shows the DataEncryptionKeyServiceImpl class:
public class DataEncryptionKeyServiceImpl implements DataEncryptionKeyService { private static final Logger LOGGER = LoggerFactory.getLogger(DataEncryptionKeyServiceImpl.class); private final ClientEncryption clientEncryption; private final Map<String, String> dataEncryptionKeysB64 = new HashMap<>(); private String KMS_PROVIDER; public DataEncryptionKeyServiceImpl(ClientEncryption clientEncryption) { this.clientEncryption = clientEncryption; } public Map<String, String> getDataEncryptionKeysB64() { LOGGER.info("=> Getting Data Encryption Keys Base64 Map."); LOGGER.info("=> Keys in DEK Map: {}", dataEncryptionKeysB64.entrySet()); return dataEncryptionKeysB64; } public String createOrRetrieveDEK(EncryptedEntity encryptedEntity) { Base64.Encoder b64Encoder = Base64.getEncoder(); String dekName = encryptedEntity.getDekName(); BsonDocument dek = clientEncryption.getKeyByAltName(dekName); BsonBinary dataKeyId; if (dek == null) { LOGGER.info("=> Creating Data Encryption Key: {}", dekName); DataKeyOptions dko = new DataKeyOptions().keyAltNames(of(dekName)); dataKeyId = clientEncryption.createDataKey(KMS_PROVIDER, dko); LOGGER.debug("=> DEK ID: {}", dataKeyId); } else { LOGGER.info("=> Existing Data Encryption Key: {}", dekName); dataKeyId = dek.get("_id").asBinary(); LOGGER.debug("=> DEK ID: {}", dataKeyId); } String dek64 = b64Encoder.encodeToString(dataKeyId.getData()); LOGGER.debug("=> Base64 DEK ID: {}", dek64); LOGGER.info("=> Adding Data Encryption Key to the Map with key: {}", encryptedEntity.getEntityClass().getSimpleName()); dataEncryptionKeysB64.put(encryptedEntity.getEntityClass().getSimpleName(), dek64); return dek64; } }
Note
Because the DEKs are stored in a map, you don't need to retrieve them again later for the JSON Schemas.
Define Entities
Spring Data MongoDB uses a POJO-centric model to implement the repositories and map the documents to the MongoDB collections.
Create the Person Entity
The following code shows the PersonEntity class:
public class PersonEntity { private ObjectId id; private String firstName; private String lastName; private String ssn; private String bloodType; public PersonEntity() { } public PersonEntity(ObjectId id, String firstName, String lastName, String ssn, String bloodType) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.ssn = ssn; this.bloodType = bloodType; } public String toString() { return "PersonEntity{" + "id=" + id + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", ssn='" + ssn + '\'' + ", bloodType='" + bloodType + '\'' + '}'; } public ObjectId getId() { return id; } public void setId(ObjectId id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getSsn() { return ssn; } public void setSsn(String ssn) { this.ssn = ssn; } public String getBloodType() { return bloodType; } public void setBloodType(String bloodType) { this.bloodType = bloodType; } }
The preceding class contains all the information needed to automate CSFLE. The class uses the @Encrypted annotation in the following ways:
The
@Encryptedclass annotation contains the SpEL expression#{mongocrypt.keyId(#target)}. This expression specifies thekeyIdof the DEK that you generated or retrieved in a previous step. CSFLE uses this DEK for encryption.The
@Encryptedannotation on thessnfield specifies that it requires a deterministic algorithm.The
@Encryptedannotation on thebloodTypefield specifies that it requires a random algorithm.
The JSON Schema generated by Spring Data MongoDB for this entity is as follows:
{ "encryptMetadata": { "keyId": [ { "$binary": { "base64": "WyHXZ+53SSqCC/6WdCvp0w==", "subType": "04" } } ] }, "type": "object", "properties": { "ssn": { "encrypt": { "bsonType": "string", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" } }, "bloodType": { "encrypt": { "bsonType": "string", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" } } } }
Configure SpEL Evaluation
The EntitySpelEvaluationExtension class enables evaluation of the SpEL expression. The following code shows this class:
public class EntitySpelEvaluationExtension implements EvaluationContextExtension { private static final Logger LOGGER = LoggerFactory.getLogger(EntitySpelEvaluationExtension.class); private final DataEncryptionKeyService dataEncryptionKeyService; public EntitySpelEvaluationExtension(DataEncryptionKeyService dataEncryptionKeyService) { this.dataEncryptionKeyService = dataEncryptionKeyService; } public String getExtensionId() { return "mongocrypt"; } public Map<String, Function> getFunctions() { try { return Collections.singletonMap("keyId", new Function( EntitySpelEvaluationExtension.class.getMethod("computeKeyId", String.class), this)); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } public String computeKeyId(String target) { String dek = dataEncryptionKeyService.getDataEncryptionKeysB64().get(target); LOGGER.info("=> Computing dek for target {} => {}", target, dek); return dek; } }
Note
This class retrieves the DEKs and matches them with the target. In this example, the target is PersonEntity.
Generate JSON Schemas and Configure MongoClient
The following sections show how to generate JSON Schemas in a Spring Data MongoDB project.
Understand the Schema Generation Process
To generate the JSON Schemas, you need the MappingContext entities that are created by the automatic configuration of Spring Data. The automatic configuration creates the MongoClient connection and the MongoTemplate.
However, to create the MongoClient with automatic encryption enabled, you need JSON Schemas.
The solution is to inject the JSON Schema creation in the autoconfiguration process by instantiating the MongoClientSettingsBuilderCustomizer bean.
Configure the Secure MongoDB Client
The following code shows the MongoDBSecureClientConfiguration class:
public class MongoDBSecureClientConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(MongoDBSecureClientConfiguration.class); private final KmsService kmsService; private final SchemaService schemaService; private String CRYPT_SHARED_LIB_PATH; private String CONNECTION_STR_DATA; private String CONNECTION_STR_VAULT; private String KEY_VAULT_DB; private String KEY_VAULT_COLL; private MongoNamespace KEY_VAULT_NS; public MongoDBSecureClientConfiguration(KmsService kmsService, SchemaService schemaService) { this.kmsService = kmsService; this.schemaService = schemaService; } public void postConstruct() { this.KEY_VAULT_NS = new MongoNamespace(KEY_VAULT_DB, KEY_VAULT_COLL); } public MongoClientSettings mongoClientSettings() { LOGGER.info("=> Creating the MongoClientSettings for the encrypted collections."); return MongoClientSettings.builder().applyConnectionString(new ConnectionString(CONNECTION_STR_DATA)).build(); } public MongoClientSettingsBuilderCustomizer customizer(MappingContext mappingContext) { LOGGER.info("=> Creating the MongoClientSettingsBuilderCustomizer."); return builder -> { MongoJsonSchemaCreator schemaCreator = MongoJsonSchemaCreator.create(mappingContext); Map<String, BsonDocument> schemaMap = schemaService.generateSchemasMap(schemaCreator) .entrySet() .stream() .collect(toMap(e -> e.getKey().getFullName(), Map.Entry::getValue)); Map<String, Object> extraOptions = Map.of("cryptSharedLibPath", CRYPT_SHARED_LIB_PATH, "cryptSharedLibRequired", true); MongoClientSettings mcs = MongoClientSettings.builder() .applyConnectionString( new ConnectionString(CONNECTION_STR_VAULT)) .build(); AutoEncryptionSettings oes = AutoEncryptionSettings.builder() .keyVaultMongoClientSettings(mcs) .keyVaultNamespace(KEY_VAULT_NS.getFullName()) .kmsProviders(kmsService.getKmsProviders()) .schemaMap(schemaMap) .extraOptions(extraOptions) .build(); builder.autoEncryptionSettings(oes); }; } }
Tip
If you want to use different backup retention policies for different clusters, you can store one DEK in each cluster. This lets you fully erase a DEK from a cluster and all backups, which might be required for legal compliance.
For more information, see CSFLE with the Java Driver.
Implement the Schema Service
The following code shows the SchemaServiceImpl class, which stores the generated JSON Schemas in a map:
public class SchemaServiceImpl implements SchemaService { private static final Logger LOGGER = LoggerFactory.getLogger(SchemaServiceImpl.class); private Map<MongoNamespace, BsonDocument> schemasMap; public Map<MongoNamespace, BsonDocument> generateSchemasMap(MongoJsonSchemaCreator schemaCreator) { LOGGER.info("=> Generating schema map."); List<EncryptedEntity> encryptedEntities = EncryptedCollectionsConfiguration.encryptedEntities; return schemasMap = encryptedEntities.stream() .collect(toMap(EncryptedEntity::getNamespace, e -> generateSchema(schemaCreator, e.getEntityClass()))); } public Map<MongoNamespace, BsonDocument> getSchemasMap() { return schemasMap; } private BsonDocument generateSchema(MongoJsonSchemaCreator schemaCreator, Class<?> entityClass) { BsonDocument schema = schemaCreator.filter(MongoJsonSchemaCreator.encryptedOnly()) .createSchemaFor(entityClass) .schemaDocument() .toBsonDocument(); LOGGER.info("=> JSON Schema for {}:\n{}", entityClass.getSimpleName(), schema.toJson(JsonWriterSettings.builder().indent(true).build())); return schema; } }
The application stores its JSON Schemas because this template also implements server-side JSON Schemas. This is a best practice for CSFLE.
Create or Update Encrypted Collections
Understand Server-Side JSON Schemas
Only the client-side JSON Schemas are required for the Automatic Encryption Shared Library. However, without server-side JSON Schemas, another misconfigured client or an admin connected directly to the cluster could insert or update documents without encrypting the fields.
To prevent this, you can use the server-side JSON Schema to enforce a field type in a document.
The JSON Schema changes with the different versions of your application, so the JSON Schemas need to be updated each time you restart your application.
Implement Encrypted Collections Setup
The following code shows the EncryptedCollectionsSetup class:
public class EncryptedCollectionsSetup { private static final Logger LOGGER = LoggerFactory.getLogger(EncryptedCollectionsSetup.class); private final MongoClient mongoClient; private final SchemaService schemaService; public EncryptedCollectionsSetup(MongoClient mongoClient, SchemaService schemaService) { this.mongoClient = mongoClient; this.schemaService = schemaService; } public void postConstruct() { LOGGER.info("=> Setup the encrypted collections."); schemaService.getSchemasMap() .forEach((namespace, schema) -> createOrUpdateCollection(mongoClient, namespace, schema)); } private void createOrUpdateCollection(MongoClient mongoClient, MongoNamespace ns, BsonDocument schema) { MongoDatabase db = mongoClient.getDatabase(ns.getDatabaseName()); String collStr = ns.getCollectionName(); if (doesCollectionExist(db, ns)) { LOGGER.info("=> Updating {} collection's server side JSON Schema.", ns.getFullName()); db.runCommand(new Document("collMod", collStr).append("validator", jsonSchemaWrapper(schema))); } else { LOGGER.info("=> Creating encrypted collection {} with server side JSON Schema.", ns.getFullName()); db.createCollection(collStr, new CreateCollectionOptions().validationOptions( new ValidationOptions().validator(jsonSchemaWrapper(schema)))); } } public BsonDocument jsonSchemaWrapper(BsonDocument schema) { return new BsonDocument("$jsonSchema", schema); } private boolean doesCollectionExist(MongoDatabase db, MongoNamespace ns) { return db.listCollectionNames() .into(new ArrayList<>()) .stream() .anyMatch(c -> c.equals(ns.getCollectionName())); } }
Support Multiple Entities
The template in this guide can support any number of entities.
To add another type of entity, create the components of the three-tier architecture as described in previous steps. Then, add the entity to the EncryptedCollectionsConfiguration class, as shown in the following example:
public class EncryptedCollectionsConfiguration { public static final List<EncryptedEntity> encryptedEntities = List.of( new EncryptedEntity("mydb", "persons", PersonEntity.class, "personDEK"), new EncryptedEntity("mydb", "companies", CompanyEntity.class, "companyDEK")); }
Because you added the @Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic") annotation to fields in the entity class, the framework uses the server-side JSON Schema to automatically generate the DEKs and create the encrypted collection.
Query by an Encrypted Field
This template implements the findFirstBySsn(ssn) method. You can use this method to find a person's document by their Social Security number, even if this field is encrypted.
Note
This method works only because a deterministic encryption algorithm is used.
Implement the Person Repository
The following code shows the PersonRepository interface:
public interface PersonRepository extends MongoRepository<PersonEntity, String> { PersonEntity findFirstBySsn(String ssn); }
Additional Resources
If you have questions, open an issue in the mongodb-java-spring-boot-csfle repository or ask a question in the MongoDB Community Forum.
To learn more about CSFLE, see the following resources:
CSFLE with the Java Driver (without Spring Data)
To learn more about Spring Data MongoDB, see the following resources:
You can view a version of this guide in video format on the MongoDB YouTube channel.