Custom AI app development using Java, spring boot, MySQL, MongoDB
Nice — I’ll give you a complete, working example you can run locally or deploy to any cloud (AWS, GCP, Railway, Render, Fly.io). The example is a small, practical AI-backed Notes app that shows how to:
- store user notes in MySQL (relational data),
- create & store text embeddings in MongoDB (vector store),
- use an LLM / embedding API to generate embeddings (via OpenAI-compatible REST call), and
- do semantic search (cosine similarity) across stored notes.
This stack uses Java + Spring Boot (REST API), Spring Data JPA for MySQL, Spring Data MongoDB for embeddings, and plain HTTP calls to the embeddings API. It’s low-complexity, production-capable, and easy to extend to more advanced vector search later (Milvus, Pinecone, or MongoDB Atlas Vector Search).
-
docker-compose.ymlto run MySQL + MongoDB locally. -
pom.xml(Spring Boot project) andapplication.ymlfor configuration. -
Java classes:
Note(JPA entity stored in MySQL)Embedding(MongoDB document storing vector + noteId)NoteRepository(Spring Data JPA)EmbeddingRepository(Spring Data MongoDB)EmbeddingService(requests embeddings from OpenAI-like API)NoteService(business logic: save note + create embedding)NoteController(REST endpoints for create note, search)
-
How to run and sample
curlcommands. -
Notes on deploying to cloud and production improvements.
Requirements to run: Java 17+, Maven, Docker (for local DBs), an OpenAI-compatible embedding API key (set as
OPENAI_API_KEYenvironment variable). You can substitute any embedding provider with a compatible REST endpoint.
Create docker-compose.yml:
version: "3.8"
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: ai_notes
MYSQL_USER: aiuser
MYSQL_PASSWORD: aipass
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 12
mongo:
image: mongo:6.0
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
volumes:
mongo_data:Run:
docker compose up -dCreate a Spring Boot app pom.xml:
<project ...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>ai-notes</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<spring.boot.version>3.1.6</spring.boot.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JPA / MySQL -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- MongoDB -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- JSON parsing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Lombok (optional, reduces boilerplate) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Dev / test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>src/main/resources/application.yml:
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/ai_notes?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
username: aiuser
password: aipass
jpa:
hibernate:
ddl-auto: update
show-sql: false
data:
mongodb:
host: localhost
port: 27017
database: ai_notes_vectors
app:
openai:
api-url: https://api.openai.com/v1/embeddings
model: text-embedding-3-smallIn production, put DB credentials and API keys in environment variables or your cloud secret manager.
Create package com.example.aionotes.
package com.example.aionotes;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AiNotesApplication {
public static void main(String[] args) {
SpringApplication.run(AiNotesApplication.class, args);
}
}package com.example.aionotes.model;
import jakarta.persistence.*;
import java.time.Instant;
@Entity
@Table(name = "notes")
public class Note {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@Column(columnDefinition = "TEXT")
private String content;
private Instant createdAt = Instant.now();
// getters and setters (or use Lombok @Data)
// ...
}package com.example.aionotes.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
@Document(collection = "embeddings")
public class Embedding {
@Id
private String id;
private Long noteId;
private List<Double> vector;
// getters and setters
// ...
}package com.example.aionotes.repository;
import com.example.aionotes.model.Note;
import org.springframework.data.jpa.repository.JpaRepository;
public interface NoteRepository extends JpaRepository<Note, Long> {}package com.example.aionotes.repository;
import com.example.aionotes.model.Embedding;
import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;
public interface EmbeddingRepository extends MongoRepository<Embedding, String> {
List<Embedding> findAll();
Embedding findByNoteId(Long noteId);
}A simple RestTemplate-based client to call the embeddings endpoint. Use your provider endpoint if different.
package com.example.aionotes.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class OpenAIEmbeddingClient {
@Value("${app.openai.api-url}")
private String apiUrl;
@Value("${app.openai.model}")
private String model;
private final String apiKey = System.getenv("OPENAI_API_KEY"); // must be set
private final RestTemplate rest = new RestTemplate();
private final ObjectMapper mapper = new ObjectMapper();
public List<Double> getEmbedding(String text) throws Exception {
if (apiKey == null || apiKey.isEmpty()) {
throw new IllegalStateException("OPENAI_API_KEY environment variable not set");
}
Map<String, Object> payload = new HashMap<>();
payload.put("model", model);
payload.put("input", text);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(apiKey);
HttpEntity<Map<String, Object>> request = new HttpEntity<>(payload, headers);
ResponseEntity<String> response = rest.postForEntity(apiUrl, request, String.class);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new RuntimeException("Embedding API responded with " + response.getStatusCode());
}
JsonNode root = mapper.readTree(response.getBody());
// path depends on provider; OpenAI returns data[0].embedding
JsonNode embeddingNode = root.path("data").get(0).path("embedding");
List<Double> vector = new ArrayList<>();
for (JsonNode n : embeddingNode) {
vector.add(n.asDouble());
}
return vector;
}
}package com.example.aionotes.service;
import com.example.aionotes.model.Embedding;
import com.example.aionotes.model.Note;
import com.example.aionotes.repository.EmbeddingRepository;
import com.example.aionotes.repository.NoteRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class NoteService {
private final NoteRepository noteRepository;
private final EmbeddingRepository embeddingRepository;
private final OpenAIEmbeddingClient embeddingClient;
public NoteService(NoteRepository noteRepository,
EmbeddingRepository embeddingRepository,
OpenAIEmbeddingClient embeddingClient) {
this.noteRepository = noteRepository;
this.embeddingRepository = embeddingRepository;
this.embeddingClient = embeddingClient;
}
public Note createNote(String title, String content) throws Exception {
Note note = new Note();
note.setTitle(title);
note.setContent(content);
Note saved = noteRepository.save(note);
// create embedding
List<Double> vector = embeddingClient.getEmbedding(content);
Embedding emb = new Embedding();
emb.setNoteId(saved.getId());
emb.setVector(vector);
embeddingRepository.save(emb);
return saved;
}
public List<Embedding> allEmbeddings() {
return embeddingRepository.findAll();
}
// cosine similarity util
public static double cosineSim(List<Double> a, List<Double> b) {
double dot = 0.0, normA = 0.0, normB = 0.0;
for (int i = 0; i < a.size(); i++) {
dot += a.get(i) * b.get(i);
normA += Math.pow(a.get(i), 2);
normB += Math.pow(b.get(i), 2);
}
if (normA == 0 || normB == 0) return 0.0;
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
}package com.example.aionotes.dto;
import com.example.aionotes.model.Note;
public class SearchResult {
private Note note;
private double score;
// constructor, getters/setters
}package com.example.aionotes.controller;
import com.example.aionotes.dto.SearchResult;
import com.example.aionotes.model.Embedding;
import com.example.aionotes.model.Note;
import com.example.aionotes.repository.NoteRepository;
import com.example.aionotes.service.NoteService;
import com.example.aionotes.service.OpenAIEmbeddingClient;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api")
public class NoteController {
private final NoteService noteService;
private final NoteRepository noteRepository;
private final OpenAIEmbeddingClient embeddingClient;
public NoteController(NoteService noteService,
NoteRepository noteRepository,
OpenAIEmbeddingClient embeddingClient) {
this.noteService = noteService;
this.noteRepository = noteRepository;
this.embeddingClient = embeddingClient;
}
@PostMapping("/notes")
public Note createNote(@RequestBody Map<String,String> body) throws Exception {
String title = body.getOrDefault("title", "Untitled");
String content = body.getOrDefault("content", "");
return noteService.createNote(title, content);
}
@GetMapping("/notes")
public List<Note> listNotes() { return noteRepository.findAll(); }
@GetMapping("/search")
public List<SearchResult> search(@RequestParam("q") String query,
@RequestParam(value="top", defaultValue="5") int top) throws Exception {
List<Double> qVec = embeddingClient.getEmbedding(query);
List<Embedding> all = noteService.allEmbeddings();
// compute similarity
List<SearchResult> results = new ArrayList<>();
for (Embedding emb : all) {
double score = NoteService.cosineSim(qVec, emb.getVector());
Optional<Note> noteOpt = noteRepository.findById(emb.getNoteId());
noteOpt.ifPresent(n -> {
SearchResult r = new SearchResult();
r.setNote(n);
r.setScore(score);
results.add(r);
});
}
return results.stream()
.sorted(Comparator.comparingDouble(SearchResult::getScore).reversed())
.limit(top)
.collect(Collectors.toList());
}
}Note: Add standard getters/setters or use Lombok (
@Data) to reduce boilerplate.
- Start DBs:
docker compose up -d- Build & run Spring Boot:
export OPENAI_API_KEY="sk-..." # your key
mvn clean spring-boot:runIf using an alternative provider, set app.openai.api-url and app.openai.model accordingly.
Create a note:
curl -X POST http://localhost:8080/api/notes \
-H "Content-Type: application/json" \
-d '{"title":"Phishing report","content":"Suspicious email asked for password reset and link pointed to bad.example.com"}'Search semantically:
curl "http://localhost:8080/api/search?q=how+to+report+phishing&top=5"You’ll get notes with score (cosine similarity) — highest scores are most semantically relevant.
- Databases: Use managed MySQL / Amazon RDS free-tier or Google Cloud Cloud SQL free credits; MongoDB Atlas has a free tier with vector search features.
- Hosting: Railway, Render, Fly.io, or a small EC2/GCE instance. All have free trial credits.
- Vector DB upgrade: For scale, move vector storage to Milvus, Pinecone, or MongoDB Atlas Vector Search (supports ANN).
- Secrets: Use environment variables or cloud secret manager (AWS Secrets Manager, GCP Secret Manager).
- Scaling: Move embedding calls to asynchronous jobs or background worker (e.g., Spring
@Asyncor a message queue) if you have high volume.
- Use ANN index (HNSW) for large-scale fast vector search (Pinecone, Milvus, or Elastic kNN).
- Add user accounts and per-user security (JWT, OAuth).
- Cache embeddings for repeated queries.
- Add rate-limiting, request quotas and billing (Stripe integration).
- Add automatic chunking for long documents and store chunk embeddings for better recall.
- Add monitoring, logging, and observability (Prometheus + Grafana, ELK).
- Don’t send PII to public LLMs unless you have consent and your provider's TOS supports that.
- Use encryption at rest (DB-level) and TLS for network traffic.
- Protect API keys; never hardcode them.
- Wrap this backend with a polished frontend (React/Vue or a no-code frontend like Bubble for quicker demos).
- Offer a hosted SaaS (multi-tenant) or white-label the service for customers.
- Add subscription & billing + onboarding flows for monetization.
- Use domain expertise (e.g., cybersecurity) to build vertical-specific prompts and templates — that’s your product moat.
If you want, I can now:
- generate the full repository structure and zippable project files, or
- produce a Dockerfile + docker-compose that runs the Spring Boot app with MySQL and Mongo together, or
- convert the embedding storage to use a MongoDB Atlas Vector Index and show exact config steps.
Which one should I create for you immediately?