Music Catalog Service
A Spring Boot REST service that demonstrates how to build a maintainable, secure, and well-tested backend system using modern Java engineering practices.

Project Snapshot
- Category
- Backend
- Language
- Java 25
- Framework
- Spring Boot
- Database
- PostgreSQL
- Build Tool
- Maven
- Repository
- Public
Additional Technologies
What This Project Covers
Music Catalog Service is a REST API for managing artists and their track catalogues. The project is designed as a learning case study, not a toy application. Every implementation decision reflects a real production concern — from how the database schema evolves over time, to how authentication is enforced, to how tests gain confidence without relying on in-memory databases.
The service supports the following operations:
- Add a new track to an artist's catalogue
- Update an artist's name
- Retrieve all tracks for a specific artist
- Display a rotating Artist of the Day
By studying this project you will see how these individually simple requirements translate into a codebase that is secure, observable, and safe to change.
Project Structure
The codebase is organised by feature rather than by technical layer. This keeps business capabilities cohesive and avoids the common problem of having dozens of unrelated classes bundled together in a service/ or repository/ directory.
com.deeptechhub.musiccatalog
├── artist/
│ ├── ArtistController.java
│ ├── ArtistService.java
│ ├── ArtistRepository.java
│ ├── Artist.java
│ ├── ArtistDTO.java
│ └── ArtistMapper.java
├── track/
│ ├── TrackController.java
│ ├── TrackService.java
│ ├── TrackRepository.java
│ ├── Track.java
│ ├── TrackDTO.java
│ └── TrackMapper.java
├── artistoftheday/
│ ├── ArtistOfTheDayController.java
│ └── ArtistOfTheDayService.java
├── common/
│ ├── config/
│ ├── exception/
│ ├── security/
│ └── web/
└── MusicCatalogServiceApplication.javaEach feature folder owns everything that feature needs: its controller, service, repository, entity, DTOs, and mapper. Adding a new feature means adding a new folder — not scattering changes across multiple technical layers.
API Design
The API follows REST conventions with a versioned base path /api/v1. All endpoints return JSON. Error responses use Spring's ProblemDetail format for a consistent error contract across all endpoints.
| Method | Endpoint | Description | Access |
|---|---|---|---|
| POST | /auth/login | Authenticate user, returns JWT | Public |
| POST | /artists | Create artist | Admin |
| GET | /artists | List all artists | Public |
| GET | /artists/{artistId} | Get artist details | Public |
| PATCH | /artists/{artistId}/name | Update artist name | Admin |
| POST | /artists/{artistId}/tracks | Add track to artist | Admin |
| GET | /artists/{artistId}/tracks | List tracks for artist | Public |
| GET | /artist-of-the-day | Get today's featured artist | Public |
Read operations are publicly accessible. Write operations require an authenticated ADMIN user. This separation is enforced at the Spring Security configuration level, not scattered across individual service methods.
Swagger UI is available locally after startup:
http://localhost:8080/swagger-ui.htmlSecurity Design
The service uses stateless JWT authentication with Role-Based Access Control.
Authentication flow
- The client sends credentials to
POST /api/v1/auth/login. - The service validates the credentials against the database and returns a signed JWT.
- The client includes the token in subsequent requests:
Authorization: Bearer <jwt-token>- Spring Security validates the token on every request — no session state is stored server-side.
Authorisation model
Security rules are defined in a central Spring Security configuration class. Public endpoints are explicitly whitelisted; everything else requires authentication. Admin-only write operations use method-level security annotations.
This makes the security posture easy to audit: there is one place to look at what is public, and one place to look at what requires privilege.
Authentication is implemented within the application rather than as a dedicated service. A dedicated auth service would add operational complexity that is out of scope for a single-service case study.
Database Design
UUID primary keys
All entities use UUID primary keys. This avoids exposing sequential integer identifiers in API responses and allows identifier generation to happen independently of the database — useful for testing and for distributed architectures.
Flyway migrations
Database schema changes are managed through versioned Flyway migration scripts. Hibernate's schema generation is disabled and set to validation only — so if the application starts and the schema does not match the entity definitions, the startup fails rather than silently altering the database.
This makes database changes explicit and auditable:
db/migration/
V1__create_artists_table.sql
V2__create_tracks_table.sql
V3__add_artist_of_day_index.sqlOptimistic locking
Mutable entities carry a @Version column. When two concurrent requests attempt to update the same entity, one will succeed and the other will receive a conflict error rather than silently overwriting a change.
Genre as an enum
Genres are modelled as a Java enum and stored as strings. A database constraint ensures only valid genre values can be persisted. This is a practical example of using the type system and the database together to enforce business rules. The enum-with-lambda-strategy-pattern guide explores this Java pattern in depth.
Artist of the Day Algorithm
The featured artist rotates through the catalogue in a fair, deterministic cycle — with no scheduled jobs, no random state, and no dependency on caches.
long epochDayUtc = LocalDate.now(ZoneOffset.UTC).toEpochDay();
int offset = (int) Math.floorMod(epochDayUtc, totalArtists);
// Artists ordered by created_at, id — same order every callBecause the offset is computed from the calendar day (not wall clock time), the result is:
- Consistent across all application instances
- Unchanged across application restarts
- Guaranteed to cycle through every artist before repeating
This is a good example of solving an operational requirement through pure computation rather than infrastructure.
Testing Strategy
The project uses a layered testing approach that gives high confidence without sacrificing speed.
Unit tests
Business logic is tested in isolation with JUnit 5 and Mockito. These tests run fast and validate the core domain rules independently of the database or HTTP layer.
Integration tests with Testcontainers
Integration tests use Testcontainers to spin up a real PostgreSQL container:
@SpringBootTest
@Testcontainers
class ArtistControllerIT {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
// ...
}This validates end-to-end behaviour — API contract, persistence, Flyway migration execution, and database constraints — against a production-equivalent database engine.
The integration tests are not substitutes for unit tests. Each layer tests what it is best at: units test logic; integration tests test boundaries.
Observability and Operations
Health and metrics
Spring Boot Actuator exposes operational endpoints used for monitoring:
| Endpoint | Purpose |
|---|---|
/actuator/health | Application health status |
/actuator/info | Application metadata |
/actuator/metrics | JVM and application metrics |
/actuator/prometheus | Prometheus-compatible scrape endpoint |
The Prometheus endpoint makes it straightforward to connect the service to a Grafana dashboard for visualising request rates, error rates, and JVM health.
Correlation ID tracing
Every request supports an X-Correlation-Id header. If the client provides one, it is propagated through the application and included in all log output for that request. If no correlation ID is provided, the service generates one.
This is a lightweight but practical step towards distributed tracing — it makes it possible to isolate all log entries belonging to a single request even when logs from multiple requests are interleaved.
The Spring Boot Correlation ID article on DeepTechHub covers this pattern in detail.
Running Locally
The only prerequisite is Docker. The application and its PostgreSQL database run inside Docker Compose.
docker compose up --buildThe API is then available at http://localhost:8080.
A default administrator account is created during database initialisation for local development:
| Username | Password |
|---|---|
admin | admin123 |
Authenticate with:
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin123"}'Use the returned JWT token in the Authorization: Bearer <token> header for admin endpoints.
Running tests
# Unit tests only
./mvnw test
# Full suite including Testcontainers integration tests (requires Docker)
./mvnw verifyKey Learning Areas
Related Content
Continue learning with supporting DeepTechHub articles and learning paths.

A Complete Guide to Testing Spring Boot Microservices
Unit, Integration, and End-to-End testing Strategies for Reliable Microservice Applications

Microservices Logging with Correlation IDs in Spring Boot
Implementing Microservices Logging with Correlation IDs in Spring Boot