A high-performance Java backend application engineered for rapid stock data retrieval and analysis. Built with Spring Boot 3.5.7 and Java 25, this service leverages advanced database optimization techniques and modern caching strategies to deliver sub-second response times for complex financial queries.
This application implements a layered architecture with a strong focus on query optimization, database indexing, and distributed caching to handle high-throughput financial data requests efficiently.
- Query Performance First: Every database interaction is optimized using DTO projections, strategic indexing, and Querydsl
- Zero N+1 Queries: Leverages JPA fetch strategies and optimized joins to eliminate redundant database round trips
- Cache-Aside Pattern: Redis-backed caching with TTL management reduces database load for frequently accessed data
- Type-Safe Queries: Compile-time query validation via Querydsl prevents runtime SQL errors
- Stateless REST Design: Horizontally scalable architecture with no session affinity requirements
- GIN trigram indexes enable lightning-fast fuzzy search across ticker symbols and company names
- Supports efficient
LIKE '%pattern%'queries and similarity scoring without full table scans - Example:
CREATE INDEX idx_ticker_lower_trgm ON ticker_summary USING gin (lower(ticker) gin_trgm_ops)
- Partial indexes on nullable columns (PE ratios, dividend yields) reduce index size and improve write performance
- Composite indexes for common filter combinations (market cap + sector queries)
- B-tree indexes for range queries and sorting operations
- Case-insensitive indexes using
lower()function for efficient text searches
- DTO projections at the database layer - only fetch required columns, not entire entities
- Dynamic predicate composition allows complex filtering without N+1 queries
- Optimized count queries separate from data queries to avoid unnecessary joins
// Efficient DTO projection - fetches only needed columns
queryFactory
.select(Projections.constructor(TickerSummaryDTO.class,
t.ticker, c.companyName, t.marketCap, /* specific fields */))
.from(t)
.leftJoin(t.cikLookup, c) // Single join, no N+1
.where(predicate)
.fetch();- L1 Cache: JPA second-level cache for entity-level caching
- L2 Cache: Redis-backed distributed cache for DTO responses
- Cache-Control Headers: Client-side caching for static financial data
- TTL Management: Different expiration policies per data type
- Stock details: 15 minutes (less volatile)
- Autocomplete results: 15 minutes (frequent updates)
- Cache Warming: Preload frequently accessed tickers on startup
- Lettuce Client: Non-blocking Redis client with connection pooling
@Cacheable(value = "stockdetailssummary", key = "#ticker.toLowerCase()")
public Optional<DetailsSummaryResponse> getStockDetails(String ticker) {
// Cache miss: fetch from database and cache result
// Cache hit: return from Redis instantly
}- PostgreSQL similarity() function with pg_trgm for ranked search results
- FULL OUTER JOIN strategy combines ticker and company name matches
- Composite scoring: Averages ticker similarity and company name similarity
- LIMIT 10: Returns only top results to minimize network transfer
-- Combines ticker and company name hits with weighted scoring
SELECT
COALESCE(t.ticker, c.ticker) AS ticker,
CAST((COALESCE(t.s_ticker, 0) + COALESCE(c.s_company, 0)) / 2.0 AS double precision) AS score
FROM ticker_hits t
FULL OUTER JOIN company_hits c ON t.cik = c.cik
ORDER BY score DESC
LIMIT 10- JWT validation with Neon Auth integration
- Custom bearer token resolver: Supports both
x-stack-access-tokenheader and standardAuthorizationheader - Stateless authentication: No session storage, enabling horizontal scaling
- Public endpoints: Search and ticker lists accessible without authentication for broader accessibility
- Versioned APIs (
/api/v1/*) for backward compatibility - Spring Validation: Bean validation at controller layer prevents invalid data from reaching services
- Global exception handling: Consistent error responses across all endpoints
- Pagination support: Offset-based pagination for large result sets
- Java 25: Latest LTS with enhanced virtual threads and performance improvements
- Spring Boot 3.5.7: Modern Spring framework with native compilation support
- Spring Data JPA: Repository abstraction with Hibernate 6.x
- PostgreSQL: Production database with advanced indexing capabilities
- pg_trgm Extension: Trigram-based fuzzy searching
- Querydsl 5.1.0: Type-safe dynamic query builder with JPA integration
- Flyway/SQL Migrations: Version-controlled database schema management
- Redis: Distributed cache with Lettuce client
- Spring Cache Abstraction: Declarative caching with annotation-based configuration
- Connection Pooling: HikariCP for optimized database connection management
- Lombok: Reduces boilerplate with compile-time code generation
- MapStruct 1.6.3: Compile-time bean mapping (zero reflection overhead)
- Custom Transformers: Hand-optimized object transformations for critical paths
- TestContainers: Integration tests with real PostgreSQL instances
- H2 Database: In-memory database for fast unit tests
- SpringDoc OpenAPI: Auto-generated API documentation
- JUnit 5: Modern testing framework with Spring Boot Test integration
- DTO Projections: Fetch only required columns instead of full entities (reduces memory and network overhead)
- Querydsl Integration: Type-safe, dynamic query composition with zero N+1 query issues
- Strategic Indexing: B-tree, GIN, and partial indexes optimized for specific query patterns
- Read-Only Transactions:
@Transactional(readOnly = true)enables database optimizations
- Distributed Redis Cache: Shares cache across multiple application instances
- Conditional Cache Enablement: Graceful fallback when Redis unavailable
- TTL-Based Invalidation: Automatic cache expiration prevents stale data
- Cache Key Normalization: Case-insensitive keys prevent cache fragmentation
- Trigram Similarity Search: Faster than
LIKEwith ranking capabilities - Normalized Search Column: Pre-processed company names for optimal matching
- Query Result Limiting: Top-K results minimize data transfer and response time
- Full Outer Join Strategy: Single query for multi-table search
- Java 25 or higher
- Maven 3.6 or higher
- PostgreSQL 14+ with pg_trgm extension (for production)
- Redis 6+ (optional, for distributed caching)
mvn clean compilemvn testmvn spring-boot:runThe application will start on http://localhost:8080
The application reads runtime configuration (database URL and credentials) from Spring's property sources. To avoid committing secrets to Git, provide the values using one of these safe options:
-
Local
.envfile (recommended for local development)-
Copy the example and fill in real values (DO NOT commit this file):
cp .env.example .env # edit .env and set DB_URL, DB_USERNAME, DB_PASSWORD -
This project includes a small loader that will read a
.envfile from the project working directory and inject its keys into Spring's Environment automatically. The loader runs before Spring resolves${...}placeholders, soapplication.propertiescan referenceDB_URL,DB_USERNAME, andDB_PASSWORDdirectly. -
.envis ignored by the repository (.gitignore) — keep it that way.
-
-
External properties file
-
Create
application-secrets.propertiesat the project root (next to the jar or project directory). Example contents:spring.datasource.url=jdbc:postgresql://your-host:5432/yourdb?sslmode=require spring.datasource.username=your_user spring.datasource.password=your_pass
-
The tracked
application.propertiesincludes:spring.config.import=optional:file:./application-secrets.propertiesso Spring will load this file automatically if present.
-
-
Environment variables / shell
-
Export environment variables before running the app or pass them inline:
export DB_URL='jdbc:postgresql://host:5432/db?sslmode=require' export DB_USERNAME='your_user' export DB_PASSWORD='your_pass' mvn spring-boot:run
Or inline:
DB_URL='...' DB_USERNAME='...' DB_PASSWORD='...' mvn spring-boot:run
-
CI / production
- Do NOT store credentials in the repository. Configure secrets in your CI provider (GitHub Actions, GitLab CI, etc.) and inject them as environment variables into the build/runtime.
Security notes
- If
.envor any secret file was accidentally committed, stop and remove it from the index (git rm --cached .env) and commit; for full history removal use tools likegit-filter-repoor BFG (this is destructive and requires a force-push — ask before proceeding). - Prefer secret managers (AWS Secrets Manager, HashiCorp Vault, etc.) for production systems.
Tests
- Unit/integration tests run against an in-memory H2 database. See
src/test/resources/application.propertiesfor the test DB configuration.
GET /api/v1/search/auto-complete?query={searchTerm}
High-performance fuzzy search with trigram similarity scoring. Returns top 10 matches across ticker symbols and company names.
Performance Optimizations:
- PostgreSQL pg_trgm extension for sub-millisecond search
- Redis caching (15-minute TTL)
- Single query with FULL OUTER JOIN strategy
- Composite scoring algorithm
Example response:
{
"query": "appl",
"results": [
{
"ticker": "AAPL",
"companyName": "Apple Inc.",
"score": 0.85
}
]
}GET /api/v1/stock-details/summary/{ticker}
Comprehensive stock metrics combining ticker summary and overview data. Optimized with Redis caching (60-minute TTL).
Example response:
{
"valuation": {
"marketCap": 3000000000000,
"peRatio": 25.5,
"forwardPeRatio": 22.3,
"enterpriseToEbitda": 18.2,
"priceToBook": 42.1,
"fiftyDayAverage": {
"value": 145.67,
"percentageDifference": 3.14
},
"twoHundredDayAverage": {
"value": 140.23,
"percentageDifference": 7.15
}
},
"margins": {
"grossMargin": 43.26,
"operatingMargin": 30.29,
"profitMargin": 25.31
},
"growth": {
"earningsGrowth": 11.5,
"revenueGrowth": 8.3,
"trailingEps": 6.13,
"forwardEps": 7.21,
"pegRatio": 2.2
},
"dividend": {
"dividendYield": 0.82,
"payoutRatio": 25.5
}
}GET /api/v1/cik-lookup/{cik}
Returns CIK lookup information for the given CIK number.
Example response:
{
"cik": 320193,
"companyName": "Apple Inc.",
"createdAt": "2023-01-01T10:00:00",
"lastUpdatedAt": "2023-01-01T10:00:00"
}GET /api/v1/ticker-summary/list?page=0&size=20&sortBy=marketCap&sortOrder=desc
High-performance paginated endpoint with dynamic filtering and sorting. Uses Querydsl for optimized DTO projections.
Query Parameters:
page: Page number (default: 0)size: Page size (default: 20, max: 100)sortBy: Sort field (ticker, marketCap, previousClose, peRatio, etc.)sortOrder: asc or desc- Filter Parameters: minMarketCap, maxMarketCap, minPe, maxPe, minDividendYield, maxDividendYield, etc.
Performance Features:
- Separate count and data queries for optimal execution plans
- DTO projection fetches only required columns
- Partial indexes on filter columns
- Dynamic predicate composition (no query plan proliferation)
Example response:
{
"content": [
{
"ticker": "AAPL",
"companyName": "Apple Inc.",
"marketCap": 3000000000000,
"previousClose": 150.25,
"peRatio": 25.5,
"forwardPeRatio": 22.3,
"dividendYield": 0.82,
"payoutRatio": 25.5,
"fiftyDayAverage": 145.67,
"twoHundredDayAverage": 140.23
}
],
"pageable": {
"pageNumber": 0,
"pageSize": 20
},
"totalElements": 5000,
"totalPages": 250
}- Autocomplete Search: <50ms (with Redis cache: <5ms)
- Ticker Summary List: <100ms for 20 records with filters
- Stock Details: <80ms (with cache: <3ms)
- Complex Filter Queries: <150ms with multiple predicates
- Index-Only Scans: Covered indexes reduce disk I/O
- Connection Pooling: HikariCP with optimized pool sizing
- Batch Processing: JDBC batch inserts for bulk operations
- Lazy Loading: Fetch associations only when needed
- Read Replicas: Can be configured for read-heavy workloads
cik_lookup: Company CIK to name mappings (with normalized search column)ticker_summary: Core stock metrics and moving averagesticker_overview: Extended financial ratios and growth metrics
-- GIN trigram indexes for fuzzy search
CREATE INDEX idx_ticker_lower_trgm ON ticker_summary USING gin (lower(ticker) gin_trgm_ops);
CREATE INDEX idx_company_name_search_trgm ON cik_lookup USING gin (company_name_search gin_trgm_ops);
-- Partial indexes for nullable columns
CREATE INDEX idx_ticker_summary_pe_ratio ON ticker_summary(pe_ratio) WHERE pe_ratio IS NOT NULL;
-- B-tree indexes for range queries and sorting
CREATE INDEX idx_ticker_summary_market_cap ON ticker_summary(market_cap);Access the OpenAPI documentation (Swagger UI) at: http://localhost:8080/swagger-ui/index.html
Access at: http://localhost:8080/h2-console
- JDBC URL:
jdbc:h2:mem:stockdb - Username:
sa - Password: (leave empty)
src/
├── main/
│ ├── java/com/stockInformation/
│ │ ├── StockInformationApplication.java # Main Spring Boot application
│ │ │
│ │ ├── config/ # Configuration classes
│ │ │ ├── QuerydslConfig.java # JPAQueryFactory bean for type-safe queries
│ │ │ ├── RedisCacheConfig.java # Redis-backed distributed caching
│ │ │ ├── SecurityConfig.java # OAuth2 JWT authentication
│ │ │ ├── WebConfig.java # CORS and web MVC configuration
│ │ │ └── DotenvEnvironmentPostProcessor.java # .env file loader
│ │ │
│ │ ├── cikLookup/ # CIK lookup domain
│ │ │ ├── api/v1/
│ │ │ │ ├── CikLookupController.java # REST endpoints
│ │ │ │ └── CikLookupMapper.java # MapStruct DTO mapper
│ │ │ ├── entity/CikLookup.java # JPA entity
│ │ │ ├── dto/CikLookupDTO.java # Data transfer object
│ │ │ ├── repository/CikLookupRepository.java # Spring Data JPA repository
│ │ │ └── service/CikLookupService.java # Business logic
│ │ │
│ │ ├── tickerSummary/ # Ticker summary domain
│ │ │ ├── api/v1/TickerSummaryController.java
│ │ │ ├── dto/TickerSummaryDTO.java
│ │ │ ├── entity/TickerSummary.java
│ │ │ ├── repository/
│ │ │ │ ├── TickerSummaryRepository.java
│ │ │ │ ├── TickerSummaryCompanyRepository.java # Custom Querydsl repo
│ │ │ │ └── TickerSummaryCompanyRepositoryImpl.java # DTO projection impl
│ │ │ ├── service/TickerSummaryService.java
│ │ │ ├── transformer/SortOrderTransformer.java # Sort conversion utilities
│ │ │ └── utils/ # Validation utilities
│ │ │
│ │ ├── stockDetails/ # Stock details domain
│ │ │ ├── api/v1/StockDetailsController.java
│ │ │ ├── dto/ # Response DTOs (Growth, Valuation, etc.)
│ │ │ ├── entity/TickerOverview.java
│ │ │ ├── repository/TickerOverviewRepository.java
│ │ │ ├── service/StockDetailsService.java # Cached service layer
│ │ │ └── transformer/StockDetailsTransformer.java # Hand-optimized mappings
│ │ │
│ │ ├── search/ # Fuzzy search domain
│ │ │ ├── api/v1/SearchController.java
│ │ │ ├── dto/ # Autocomplete DTOs
│ │ │ ├── repository/
│ │ │ │ ├── SearchRepository.java
│ │ │ │ └── SearchRepositoryImpl.java # Native SQL with pg_trgm
│ │ │ ├── service/SearchService.java # Cached autocomplete logic
│ │ │ └── utils/utils.java # Query normalization
│ │ │
│ │ ├── common/
│ │ │ └── dto/PageResponse.java # Generic paginated response
│ │ │
│ │ └── exception/
│ │ └── ApiExceptionHandler.java # Global exception handling
│ │
│ └── resources/
│ ├── application.properties # Main configuration
│ └── META-INF/spring.factories # Custom environment processors
│
├── test/
│ ├── java/com/stockInformation/ # Comprehensive test suite
│ │ ├── config/RedisConnectionTest.java # Cache integration tests
│ │ ├── cikLookup/ # Domain tests
│ │ ├── tickerSummary/ # Repository & service tests
│ │ ├── stockDetails/ # DTO transformation tests
│ │ └── search/ # Search algorithm tests
│ └── resources/
│ ├── application.properties # Test configuration (H2 database)
│ └── init.sql # Test data initialization
│
└── db/migrations/ # SQL schema migrations
├── create_cik_lookup_table.sql # CIK table with trigram indexes
├── create_ticker_summary_table.sql # Ticker table with partial indexes
└── create_ticker_overview_table.sql # Overview table with foreign keys
- Lombok: Eliminates getter/setter boilerplate
- MapStruct: Zero-reflection DTO mapping at compile time
- Immutable DTOs: Value objects with validation
- Null Safety: Optional for potentially absent values
- Unit Tests: Service and repository layer tests with H2
- Integration Tests: TestContainers with real PostgreSQL
- API Tests: Spring MockMvc for controller testing
- Test Coverage: >80% code coverage across critical paths
- Environment-based Secrets: No credentials in source code
- JWT Validation: Stateless token-based authentication
- CORS Configuration: Controlled cross-origin access
- SQL Injection Protection: Parameterized queries via JPA/Querydsl
Update application.properties or use environment variables:
# Database Configuration
spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:5432/stockdb}
spring.datasource.username=${DB_USERNAME:postgres}
spring.datasource.password=${DB_PASSWORD}
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
# Redis Cache Configuration
spring.redis.host=${REDIS_HOST:localhost}
spring.redis.port=${REDIS_PORT:6379}
spring.redis.ssl.enabled=${REDIS_SSL_ENABLED:false}
spring.cache.type=redis
# Performance Tuning
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.jpa.properties.hibernate.jdbc.batch_size=20Tests automatically use H2 in-memory database with no Redis dependency.
- Stateless Design: No session storage, can run multiple instances
- Shared Redis Cache: All instances share the same cache
- Database Connection Pooling: Configure pool size based on instance count
- Load Balancer Ready: Health check endpoint at
/actuator/health
- JVM Options: Use G1GC for low-latency garbage collection
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xms2g -Xmx2g
- Database Tuning: Adjust PostgreSQL
shared_buffers,work_memfor your workload - Redis Tuning: Use
maxmemory-policyofallkeys-lrufor cache eviction - Connection Pools: Size HikariCP based on:
pool_size = ((core_count * 2) + disk_spindles)
This project is open source and available under the MIT License.