Context Search With AWS Bedrock, Cohere Model, and Spring AI
In this article, learn how to build a simple Java application using Spring AI and Amazon Bedrock to generate and compare text embeddings with Cohere’s Embed Multilingual v3 model.
Join the DZone community and get the full member experience.
Join For FreeToday, we will create simple applications using the Cohere Embed Multilingual v3 model via Amazon Bedrock and Spring AI.
We’ll skip over basic Spring concepts like bean management and starters, as the main goal of this article is to explore the capabilities of Spring AI and Amazon Bedrock.
The full code for this project is available in the accompanying GitHub repository. To keep this article concise, I won’t include some pre-calculated values and simple POJOs here — you can find them in the repo if needed.
What Are Embeddings?
Before we start code implementation, let's discuss embeddings.
In the Spring AI documentation, we find the following definition of embeddings:
"Embeddings are numerical representations of text, images, or videos that capture relationships between inputs."
Embeddings convert text, images, and video into arrays of floating-point numbers called vectors. These vectors are designed to capture the meaning of the text, images, and videos. The length of the embedding array is called the vector’s dimensionality.
Key points we should pay attention to:
- Numerical representation of text (also applicable for images and videos, but let’s focus just on texts in this article)
- Embeddings are vectors. And as every vector has coordinates for each dimension it exists, we should think about embeddings as a coordinate of our input in “Text Universe”
As with every other vector, we can find the distance between two embeddings. The closer the two embeddings are to each other, the more similar their context. We will use this approach in our application.
Determining the Scope of Our Future Application
Let’s imagine that we have an online shop with different mattresses. Every single item has its ID and description. We need to create a module, that will receive users input describing the item the user wants to find or buy and returns 5 most relevant products to this query.
We will achieve this goal using embeddings. Steps we need to implement
- We will fetch embeddings (vector representation) of our existing products and store them. I’ll not show this step in this article because it will be similar to one we will explore later. But you can find precalculated embeddings to use in your code in the GitHub repo I previously shared.
- We will call the Amazon Bedrock embeddings API for each user input.
- We will compare user input embeddings with precalculated embeddings of our item description. We will leverage the Cosine Similarity approach to find the closest vectors.
Implementation
Note: Please be aware that executing this application may cost you some money for running AWS Bedrock.
Step 1. Generate AWS Keys and Enable the Foundational Model
If you don’t have an active AWS access key, do the following steps (copied and pasted from this SOF thread):
- Go to: http://aws.amazon.com/
- Sign up and create a new account (they'll give you the option for a 1-year trial or similar)
- Go to your AWS account overview
- Account menu in the upper-right (has your name on it)
- sub-menu: Security Credentials
After you have your keys generated you should choose and enable the foundational model in Bedrock. Go to Amazon Bedrock, and from the Model Access menu on the left, configure access to the Cohere Embed Multilingual model.
Step 2. Set Up a Project
To quickly generate a project template with all necessary dependencies, one may use https://start.spring.io/
In our example, we’ll be using Java 17 and Spring Boot 3.4.1. Also, we need to include the following dependency:
Amazon Bedrock: This dependency provides us with smooth integration with Amazon Bedrock just by writing a couple lines of code and a few lines of configurations.
After clicking generate, open the downloaded files in the IDE you are working on, and validate that all necessary dependencies exist in pom.xml.
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bedrock-ai-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-M6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
At the moment of writing this article, Spring AI version 1.0.0-M6 has not yet been published in the central Maven repository and is only available in the Spring repository. That’s why we need to add a link to that repo in our pom.xml
as well:
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
Step 3. Set Up the Configuration File
As a next step, we need to configure our property file. By default, Spring uses application.yaml
or application.properties
file. In this example, I’m using the YAML format. You may reformat code into .properties
if you feel more comfortable working with this format.
Here are all the configs we need to add to the application.yaml
file:
spring:
application:
name: aiembeddingsbedrock
ai:
bedrock:
aws:
access-key:
secret-key:
cohere:
embedding:
model: cohere.embed-multilingual-v3
enabled: true
Access-key, secret-key
Copy and paste the security credential pair we generated in step 1. Make sure you are not pushing these credentials to the remote repo.
Model
We will be using cohere.embed-multilingual-v3
. We may also use the Titan embedding model, but in this case, the config file should be set in a slightly different way. You may find more information in the Spring AI docs.
As the main purpose of this article is to show the ease of Spring AI integration with Amazon Bedrock embedding models, we will not go deeper into other configurations. You may find more config options in the Spring docs.
Step 4. Create Resource Files
Let’s create two files in the resource folder.
The first one is the JSON-formatted “database” of items in our shop. Every item will have the following parameters: Id, Name, and Description. I named this file samples.json and saved it in the resource folder.
[
{
"id": 1,
"name": "DreamSoft Memory Foam",
"description": "Queen size, memory foam, medium firmness, cooling gel layer, hypoallergenic cover, pressure-relieving design, motion isolation, anti-bacterial protection, reinforced edges, eco-friendly materials, breathable fabric."
},
{
"id": 2,
"name": "SleepWell Hybrid Comfort",
"description": "King size, hybrid (foam + springs), medium-firm, breathable fabric, motion isolation, orthopedic support, reinforced edges, dust-mite resistant, zoned pressure relief, temperature-regulating cover, moisture-wicking material."
},
{
"id": 3,
"name": "OrthoRest Firm Support",
"description": "Full size, high-density foam, firm support, orthopedic design, anti-bacterial cover, hypoallergenic materials, motion isolation, lumbar support zone, durable construction, soft knit fabric."
},
{
"id": 4,
"name": "CloudNine Plush Top",
"description": "California King, pillow-top design, soft firmness, pocketed coils, moisture-wicking fabric, pressure-relief zones, motion isolation, anti-microbial treatment, reinforced edge support, luxury plush feel."
},
{
"id": 5,
"name": "EcoSleep Organic Latex",
"description": "Queen size, natural latex, medium firmness, organic cotton cover, eco-friendly materials, hypoallergenic, durable support, breathable construction, cooling airflow design, anti-dust-mite protection."
},
{
"id": 6,
"name": "ZenBalance Hybrid Pro",
"description": "King size, hybrid latex and springs, firm support, pressure relief zones, cooling technology, orthopedic certified, reinforced lumbar support, anti-bacterial protection, soft-touch fabric, edge stability."
},
{
"id": 7,
"name": "SnugFit Dual Comfort",
"description": "Twin size, reversible (soft and firm sides), gel-infused memory foam, anti-microbial cover, motion isolation, breathable materials, cooling effect, ergonomic design, pressure relief, durable construction."
},
{
"id": 8,
"name": "TranquilDream Euro Top",
"description": "Full size, euro-top cushion, medium-soft, breathable layers, reinforced edges, plush comfort, pressure relief, orthopedic support, anti-allergy treatment, soft-touch fabric."
},
{
"id": 9,
"name": "SleepWell Firm Hybrid",
"description": "Queen size, pocket springs and latex, extra firm support, temperature-regulating fabric, breathable mesh cover, motion isolation, reinforced lumbar zone, anti-microbial coating, edge support, durable foam layers."
},
{
"id": 10,
"name": "CloudNest Ultra Soft",
"description": "Twin size, ultra-soft memory foam, adaptive contouring, hypoallergenic materials, plush comfort, ergonomic design, motion isolation, cooling gel layer, anti-dust-mite treatment, durable cover."
},
{
"id": 11,
"name": "GrandRest Luxury Foam",
"description": "California King, high-resilience foam, medium firmness, pressure-relieving layers, durable support, orthopedic comfort, breathable construction, anti-allergy cover, moisture-wicking fabric, reinforced durability."
},
{
"id": 12,
"name": "NatureSleep Bamboo Bliss",
"description": "Queen size, bamboo-infused foam, medium-plush, dust-mite resistant, cooling effect, eco-friendly construction, breathable layers, ergonomic support, anti-bacterial finish, luxury feel."
},
{
"id": 13,
"name": "BackCare OrthoFlex",
"description": "King size, orthopedic support, extra firm, reinforced lumbar zone, breathable mesh cover, motion isolation, pressure-relief technology, anti-allergy fabric, durable construction, anti-microbial treatment."
},
{
"id": 14,
"name": "EcoHaven Pure Latex",
"description": "Full size, 100% natural latex, firm support, moisture-resistant organic cotton cover, eco-friendly production, anti-bacterial protection, breathable layers, ergonomic support, durable edge reinforcement, motion control."
},
{
"id": 15,
"name": "SereneNight Cooling Gel",
"description": "Twin XL, gel-infused foam, medium-soft, anti-sag technology, eco-friendly fabric, breathable layers, reinforced edges, motion isolation, cooling airflow, pressure relief."
},
{
"id": 16,
"name": "AirFlow Tech Hybrid",
"description": "King size, hybrid springs and foam, airflow channels, medium-firm, ergonomic design, orthopedic support, durable frame, anti-bacterial cover, temperature control, reinforced edge support."
},
{
"id": 17,
"name": "HavenCloud Orthopedic",
"description": "Queen size, orthopedic memory foam, medium firmness, zoned pressure relief, anti-bacterial fabric, motion isolation, breathable construction, hypoallergenic, edge reinforcement, moisture control."
},
{
"id": 18,
"name": "EliteRest Plush Feel",
"description": "California King, plush top layer, responsive foam, moisture-wicking fabric, ultra-soft finish, ergonomic design, breathable mesh, motion control, reinforced edges, luxury feel."
},
{
"id": 19,
"name": "SleepGuard Anti-Allergy",
"description": "Full size, hypoallergenic foam, medium-firm, mite-resistant, reinforced support core, anti-dust-mite treatment, breathable design, motion isolation, ergonomic shape, cooling effect."
},
{
"id": 20,
"name": "SnuggleEase Memory Cloud",
"description": "Twin size, cloud-like memory foam, medium-plush, heat-dissipating layers, soft knit cover, motion isolation, breathable fabric, anti-bacterial treatment, ergonomic shape, pressure relief."
}
The second one is a list of embeddings of the product description. I executed embeddings API in a separate application and saved responses for every single product into a separate file, embeddings.json
. I’ll not share the whole file here, as it will make the article unreadable, but you still can download it from the GitHub repo of this project I shared at the beginning of the article.
Step 5. Create Embeddings Service
Now, let’s create the main service of our application -> embedding service.
To integrate our application with the embeddings API, we need to autowire EmbeddingModel
. We have already configured Bedrock embeddings in the application.yaml
. Spring Boot will automatically create and configure the instance (Bean) of EmbeddingModel
.
To fetch embeddings for a particular String or text, we just need to write one line of code:
EmbeddingResponse embeddingResponse = embeddingModel.embedForResponse(List.of(text));
Let’s see what the whole service looks like:
@Service
public class EmbeddingsService {
private static List<Product> productList = new ArrayList<>();
private static Map<Integer, float[]> embeddings = new HashMap<>();
@Autowired
private EmbeddingModel embeddingModel;
@Autowired
private SimilarityCalculator similarityCalculator;
@PostConstruct
public void initProducts() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("samples.json");
if (inputStream != null) {
// map JSON into List<Product>
productList = objectMapper.readValue(inputStream, new TypeReference<List<Product>>() {
});
System.out.println("Products loaded: List size = " + productList.size());
} else {
System.out.println("File samples.json not found in resources.");
}
embeddings = loadEmbeddingsFromFile();
}
public Map<Integer, float[]> loadEmbeddingsFromFile() {
try {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("embeddings.json");
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(inputStream, new TypeReference<Map<Integer, float[]>>() {
});
} catch (Exception e) {
System.err.println("Error loading embeddings from file: " + e.getMessage());
return null;
}
}
public void getSimilarProducts(String query) {
EmbeddingResponse embeddingResponse = embeddingModel.embedForResponse(List.of(query));
List<ProductSimilarity> topSimilarProducts = similarityCalculator.findTopSimilarProducts(embeddingResponse.getResult().getOutput(),
embeddings,
productList,
5);
for (ProductSimilarity ps : topSimilarProducts) {
System.out.printf("Product ID: %d, Name: %s, Description: %s, Similarity: %.4f%n",
ps.getProduct().getId(),
ps.getProduct().getName(),
ps.getProduct().getDescription(),
ps.getSimilarity());
}
}
Let’s deep dive into this code:
- In the
@postconstruct
method, we are loading our resources into collections. The list of Products reads our products fromsamples.json
. The product is a POJO with ID, name, and description fields. We also load precalculated embeddings of our products from another fileembeddings.json
. We will need these embeddings later when we look for the most similar product. - The most important method in our service is
getSimilarProducts
which will receive user queries, fetch its embeddings using embeddingModel, and calculate similarities with our existing products. We will take a closer look atsimilarityCalculator.findTopSimilarProducts
a little bit later in this article. After receiving a list of similarities, we will print the top N similar products in the following format:- Product ID, Name, Description, Similarity (a number between 0 and 1)
To calculate similarities, we introduced SimilarityCalculator
Service. Let’s take a deeper look at its implementation.
@Service
public class SimilarityCalculator {
public float calculateCosineSimilarity(float[] vectorA, float[] vectorB) {
float dotProduct = 0.0f;
float normA = 0.0f;
float normB = 0.0f;
for (int i = 0; i < vectorA.length; i++) {
dotProduct += vectorA[i] * vectorB[i];
normA += Math.pow(vectorA[i], 2);
normB += Math.pow(vectorB[i], 2);
}
return (float) (dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)));
}
public List<ProductSimilarity> findTopSimilarProducts(
float[] queryEmbedding,
Map<Integer, float[]> embeddings,
List<Product> products,
int topN) {
List<ProductSimilarity> similarities = new ArrayList<>();
for (Product product : products) {
float[] productEmbedding = embeddings.get(product.getId());
if (productEmbedding != null) {
float similarity = calculateCosineSimilarity(queryEmbedding, productEmbedding);
similarities.add(new ProductSimilarity(product, similarity));
}
}
return similarities.stream()
.sorted((p1, p2) -> Double.compare(p2.getSimilarity(), p1.getSimilarity()))
.limit(topN)
.toList();
}
ProductSimilarity
is a POJO class containingProduct
andsimilarity
fields. You can find the code for this class in the GitHub repo.calculateCosineSimilarity
is the method used to find the most similar descriptions to user queries. Cosine similarity is one of the most popular ways to measure the similarity between embeddings. Explaining the exact workings of cosine similarity is beyond the scope of this article.findTopSimilarProducts
is a method called from our embedding service. It calculates similarities with all products, sorts them, and returns the top N products with the highest similarity.
Step 5: Execute Application
We will execute this application directly from code without using any Rest Controllers and API calls. If you want to make this app a little bit more flexible by triggering it by an endpoint execution you may use an approach similar to the one used in this article.
@SpringBootApplication
public class AiEmbeddingsApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = new SpringApplicationBuilder(AiEmbeddingsApplication.class)
.web(WebApplicationType.NONE)
.run(args);
run.getBean(EmbeddingsService.class).getSimilarProducts("anti-allergy king-size mattress");
}
We are executing our code in the last line of the method, fetching the bean from the context and executing the getSimilarProducts
method with a provided query.
In our query, we’ve included 3 keywords: anti-allergy, king-size, mattress
. Let’s execute our code and validate the result.
To start our application, we need to run the following command:
mvn spring-boot:run
In a couple of seconds after executing, we may see the following result in the console:
Product ID: 13, Name: BackCare OrthoFlex, Description: King size, orthopedic support, extra firm, reinforced lumbar zone, breathable mesh cover, motion isolation, pressure-relief technology, anti-allergy fabric, durable construction, anti-microbial treatment., Similarity: 0,6107
Product ID: 16, Name: AirFlow Tech Hybrid, Description: King size, hybrid springs and foam, airflow channels, medium-firm, ergonomic design, orthopedic support, durable frame, anti-bacterial cover, temperature control, reinforced edge support., Similarity: 0,5984
Product ID: 2, Name: SleepWell Hybrid Comfort, Description: King size, hybrid (foam + springs), medium-firm, breathable fabric, motion isolation, orthopedic support, reinforced edges, dust-mite resistant, zoned pressure relief, temperature-regulating cover, moisture-wicking material., Similarity: 0,5964
Product ID: 5, Name: EcoSleep Organic Latex, Description: Queen size, natural latex, medium firmness, organic cotton cover, eco-friendly materials, hypoallergenic, durable support, breathable construction, cooling airflow design, anti-dust-mite protection., Similarity: 0,5874
Product ID: 1, Name: DreamSoft Memory Foam, Description: Queen size, memory foam, medium firmness, cooling gel layer, hypoallergenic cover, pressure-relieving design, motion isolation, anti-bacterial protection, reinforced edges, eco-friendly materials, breathable fabric., Similarity: 0,5836
We can see that Product 13 has the highest similarity, as it is both a king-sized and hypoallergenic mattress. Even though it doesn't exactly match the search query, it closely aligns with what we were looking for. All of the other recommended mattresses are either king-sized or hypoallergenic.
Conclusion
Spring AI is a great tool that helps developers smoothly integrate with different AI models. At the moment of writing this article, Spring AI supports 10 embedding models, including but not limited to Ollama and Open AI. On the other hand, Amazon Bedrock offers a choice of high-performing foundation models (FMs) from leading AI companies like AI21 Labs, Anthropic, Cohere, DeepSeek, Luma, Meta, Mistral AI, poolside (coming soon), Stability AI, and Amazon through a single API, along with a broad set of capabilities you need to build generative AI applications with security, privacy, and responsible AI.
I hope you found this article helpful and that it will inspire you to explore Spring AI and AWS Bedrock more deeply.
Opinions expressed by DZone contributors are their own.
Comments