AI Search & JHipster

Adding AI Semantic Search to a JHipster App with pgvector and OpenAI Embeddings

The fastest way to add AI semantic search to a JHipster application is generator-jhipster-ai-postgresql — an open-source JHipster 9 blueprint I maintain that adds PostgreSQL pgvector support to generated apps: automatic OpenAI embedding generation on every create and update, HNSW indexes for fast approximate nearest-neighbor search, cosine-similarity filtering, Liquibase migrations that use real vector(N) column types, and a natural-language search bar in the generated Angular UI. You declare vector fields with two JDL annotations; the blueprint generates everything else. This article explains what it generates and the design decisions behind it — the same code that powers semantic search in the Saathratri platform in production.

Is there a JHipster blueprint that supports pgvector?

Yes — generator-jhipster-ai-postgresql, published on npm and compatible with JHipster 9.1.0. It is a side-by-side blueprint: JHipster's standard generators keep doing what they do, and the blueprint layers pgvector-backed semantic search (plus human-readable foreign-key display) on top of the entities you mark. Install and use it like any JHipster blueprint:

npm install -g generator-jhipster-ai-postgresql
jhipster --blueprints ai-postgresql

Declaring vector fields in JDL

Semantic search is opt-in per field. You declare an embedding field as a Blob with two custom annotations — VECTOR marks it as a pgvector embedding field, and the second annotation sets the vector dimension:

entity Tag {
  id UUID
  name String maxlength(100) required
  description String maxlength(255)
  @customAnnotation("VECTOR") @customAnnotation("1536") nameEmbedding Blob
  @customAnnotation("VECTOR") @customAnnotation("1536") descriptionEmbedding Blob
}

The naming convention does the wiring: nameEmbedding derives from name, descriptionEmbedding from description. On every create and update, the blueprint-generated code takes the source field's text, calls the OpenAI Embedding API (text-embedding-3-small, 1536 dimensions, via Spring AI), and stores the vector alongside the row. There is no separate indexing pipeline to build, schedule, or forget about — the entity write path is the indexing pipeline.

What the blueprint generates

  • Schema the declarative way: Liquibase changelogs are automatically patched from blob to vector(1536) column types, so migrations stay reviewable and repeatable. The JDBC URL gets stringtype=unspecified for seamless varchar-to-vector casting, and a PgVectorConverter with autoApply=true handles float[] ↔ PostgreSQL vector serialization transparently in JPA.
  • HNSW indexes on every vector column, created automatically — approximate nearest-neighbor search that stays fast as tables grow, instead of sequential scans over raw vectors.
  • Embedding generation service: an EmbeddingConfiguration wired for Spring AI and OpenAI embeddings, invoked on entity create/update — plus automatic embedding migration on startup: like Liquibase for your vectors, any rows missing embeddings get them generated when the app boots. Adding vector search to an entity with existing data just works.
  • Cosine-similarity search with a relevance threshold: queries filter at 0.8 cosine distance, so unrelated rows don't pad the results. A search for “leopard” over name embeddings returns Cats and Dogs — not your entire table ranked by decreasing irrelevance.
  • A generated Angular search UI: list pages for vector-enabled entities get an AI search bar where users type natural-language queries. When an entity has multiple embedding fields, checkboxes let the user choose which fields to search (all selected by default); results from multiple fields are merged and deduplicated by ID.
  • DTO handling: embeddings travel as List<Float> in DTOs for frontend display (the UI shows a three-value preview like [0.01234, -0.06789, 0.12345, ...]), while stored as float[] in the JPA entity.

Why this design, and not a separate vector database?

For most JHipster applications, keeping vectors in PostgreSQL next to the transactional data is the right call: one database to operate, no synchronization pipeline between your source of truth and your search index, and Liquibase governs the whole schema. pgvector with HNSW comfortably covers the “semantic search over my application's entities” use case. A dedicated vector platform earns its operational cost when you have very large embedding workloads or cross-application search — not when you want your Product list page to understand that “boat” should match “ferry.”

The blueprint approach also beats hand-rolling the same stack (Spring AI + pgvector + Liquibase is maybe a week of careful work to do well): every entity you add later gets the same treatment for free, and fixes to the generator flow into every regeneration. It's the difference between a pattern you documented and a pattern you automated.

Scaling up: Cassandra and multi-database platforms

The same JDL-annotation approach extends across my other blueprints. generator-jhipster-cassandra implements vector search on Apache Cassandra 5.0 using SAI (Storage-Attached Indexes) with ANN queries — same @customAnnotation("VECTOR") declaration, different engine underneath. And generator-jhipster-orchestrator composes both blueprints to generate a complete microservices platform with PostgreSQL/pgvector and Cassandra services side by side from one JDL file — each service getting its database's idiomatic persistence, migrations, and search. That's the architecture Saathratri runs on.

FAQ

Which embedding model does generator-jhipster-ai-postgresql use?

OpenAI's text-embedding-3-small at 1536 dimensions, called through Spring AI. The dimension is declared per field in the JDL annotation, so the generated schema, converter, and queries all agree on vector(1536).

Do embeddings stay in sync with the data?

Yes, by construction: embeddings are regenerated whenever the source field changes on create or update, and a startup migration backfills any rows missing embeddings. There is no separate indexer to drift out of sync — a failure mode JHipster's own Elasticsearch integration documentation warns about.

Can users search across multiple fields?

Yes. The generated search bar queries all of an entity's embedding fields by default, merges results, and deduplicates by ID; checkboxes let users narrow the search to specific fields, which changes both the result set and the relevance ranking.

Who maintains generator-jhipster-ai-postgresql?

Amar P. Patel — software engineer and founder of Saathratri, and author of the companion generator-jhipster-cassandra and generator-jhipster-orchestrator blueprints. The blueprint generates the semantic-search services running in the Saathratri hospitality platform, so it's maintained against production needs, not demos. Source is on GitHub.