Spring Boot with PostgreSQL Multi-Schema

🌟 Introduction

When you're building a backend system where all users share the same application instance, but you still want a clean, modular architecture without microservices, multi-schema support is a powerful yet underutilized option.

In this post, we’ll walk through:

  • Why and when to use PostgreSQL multi-schema with Spring Boot

  • How to structure a modular monolith using JPA

  • How cross-schema entity relationships, joins, and transactions work

  • Production-grade considerations and anti-patterns


🧠 Use Case

β€œI have a single Spring Boot application, one shared PostgreSQL database, and a common user base. I want to logically separate domains like billing, authentication, user management, and auditingβ€”without using microservices or tenant-based sharding.”

This is a perfect fit for:

  • Modular monolith architecture

  • Single-tenant but domain-isolated applications

  • Clean layering of domain logic and future scalability


πŸ—‚οΈ Schema Design

Here’s the schema layout we’ll follow:

Schema
Domain
Tables

users

User Management

profile, settings

auth

Authentication

user_credentials, sessions

billing

Payments & Invoicing

invoice, payment

audit

Activity Logging

activity_logs

This gives us:

  • Logical separation

  • Easier domain-based access control

  • Organized migrations

  • Future service modularity

❔ Why Multi-Schema?

  • πŸ”’ Security: Sensitive authentication data isolated in auth schema

  • πŸ“Š Data Organization: Business domains clearly separated

  • πŸ”§ Maintainability: Schema-specific changes don't affect other domains

  • ⚑ Performance: Targeted indexing and optimization per schema

  • πŸ‘₯ Team Collaboration: Different teams can work on different schemas

  • πŸ›‘οΈ Access Control: Fine-grained database permissions per schema


πŸ”§ Project Setup

We use standard Spring Boot with:

  • JPA (Hibernate)

  • PostgreSQL (single database)

  • HikariCP (connection pool)

  • Flyway (for schema-based migrations)


🧱 Entity Definitions with Schema Mapping

1. UserProfile in users schema

2. Invoice in billing schema β€” Cross-Schema Join

βœ… This is an example of explicit ORM cross-schema mapping.

  1. UserCredentials in auth schema

  1. AuditLog in auth Schema


πŸͺ½ Flyway Schema Migration

V2__Create_users_table.sql

V3__Create_billing_tables.sql


πŸ” Joining Entities Across Schemas

Hibernate supports joins across schemas as long as you’ve mapped the entities correctly.

Query: Get all invoices for users with name 'Alice'

➑️ This generates SQL:

πŸ’‘ Use DTO projection when returning large datasets:


🚫 Avoiding Over-Normalization

A common mistake: mapping every domain with deep foreign keys.

For example:

Instead:

  • Use service-level joins if needed

  • Keep domain boundaries loose

  • Avoid deep nested fetches (especially auth ↔ billing ↔ audit)


πŸ”„ Transactions Across Schemas

PostgreSQL handles transactions natively across schemas (same DB connection). So this just works:

No need for XA transactions or multiple EntityManagers.


πŸ“ Architecture Benefits

Feature
Value

Logical separation

βœ… Strong domain boundaries

Team modularity

βœ… Teams can own schema + module

Cross-schema joins

βœ… Supported natively

Performance impact

❗ Slight schema overhead in large joins

Migration control

βœ… Schema-based versioning with Flyway

Security

βœ… Grant schema-level privileges easily


βš™οΈ Production Recommendations

  • Use @EntityGraph or DTOs to reduce unnecessary joins

  • Log SQL in non-prod with spring.jpa.show-sql=true

  • Monitor with pg_stat_statements for expensive queries

  • Avoid circular dependencies in entity mappings


πŸ“Œ Summary

Using PostgreSQL multi-schema with Spring Boot JPA provides a powerful middle ground between monoliths and microservices. You get:

  • Logical modularity

  • Clean database boundaries

  • Transaction safety

  • Simpler codebase management

This approach is ideal for teams building well-structured, single-tenant applications that are future-proof and domain-oriented.

Last updated