Lessons
1 · Java Foundations
2 · Object-Oriented Java
3 · Core Java
4 · Collections and Generics
5 · Functional Java
6 · The JVM and Concurrency
7 · Tools and Craft
- 21File I/O and Serialization
- 22Debugging: Errors, Stack Traces, the Debugger and Logging
- 23Maven: What It Does and How to Use It
- 24Git for Java Projects: Commits, Branches, Merging and Pull Requests
- 25Testing with JUnit and Mockito: Unit Tests, Mocks and Their Limits
- 26Project 1: Build an Expense Tracker CLI in Plain Java
8 · Databases and JDBC
9 · Web and Spring
10 · Persistence with JPA
11 · Secure, Tested, Production-Ready
- 36Security Fundamentals and Spring Security: Passwords, JWT, Access Control, CORS and Secrets
- 37Testing Spring Boot Applications: Slice Tests, MockMvc, Real Databases and the Test Pyramid
- 38Production Practices: Configuration, Logging, Docker and Health Checks for Spring Boot
- 39Project 3: The Job Application Tracker API, Assembled and Reviewed
Lesson 39 · Secure, Tested, Production-Ready
Project 3: The Job Application Tracker API, Assembled and Reviewed
Put the whole backend together: the final architecture, the requirements checklist, how each article contributed, how to build, run and test it, an honest list of limitations, and the questions an interviewer is likely to ask about it.
For
Learners who finished the Spring Boot, JPA, security and testing articles and want to treat the result as a portfolio project they can build, run, explain and defend.
Level
Before you start
Articles 30 to 38. The project was built and tested on JDK 21 and JDK 25 with Spring Boot 4.1.1 against PostgreSQL 18.6. The Docker files were not run.
On this page
Key takeaways
- The final application is a layered Spring Boot API with controllers, services and Spring Data repositories, PostgreSQL schema management with Flyway, JPA relationships, JWT login with bcrypt passwords, per-user data access, consistent problem-details errors, health checks and a Dockerfile.
- It has 125 automated tests (unit, slice and integration, most database tests on a real PostgreSQL) with 96% line coverage, and the main protections were checked with a mutation experiment.
- A project is portfolio-ready when you can build and run it from a clean clone, explain every design decision, and state its limits honestly.
- Interviewers ask about the decisions: why DTOs, why the owner is in every query, why optimistic locking, how N+1 was found, how secrets are handled and what you would improve.
Over the last articles you built one application a piece at a time. This article assembles it, checks it against its requirements, and prepares you to explain it. It is the third project of the course, after the Expense Tracker CLI and the PostgreSQL version.
What you will learn
#- The final architecture and where each concept lives.
- How to build, run and test the project from scratch.
- A requirements checklist, an honest limitations list, and extension ideas.
- How to present the project in an interview.
What it does
#The Job Application Tracker API lets a registered user keep track of the jobs they are pursuing: create applications, move them through the stages (WISHLIST, APPLIED, INTERVIEWING, OFFER, REJECTED, WITHDRAWN), record interviews, filter and page the list, and edit safely when several clients are involved. Every user sees only their own data.
Architecture
#HTTP request | vSpring Security filter chain JWT verification, deny by default, CORS, security headers | vControllers AuthController, JobApplicationController (HTTP only, DTO records) | vServices AuthService, TokenService, JobApplicationService (rules, transactions) | vSpring Data repositories JobApplicationRepository, AppUserRepository (every query scoped to the owner) | vHibernate / JPA entities JobApplication, Interview, AppUser (Flyway owns the schema) | vPostgreSQLLayer or concern | Main classes | Introduced in |
|---|---|---|
Application and configuration |
| |
Domain |
| |
Persistence |
| |
Business rules |
| |
HTTP API and DTOs |
| |
Error handling |
| |
Security |
| |
Operations | Actuator, | |
Tests | 125 tests in 16 files |
The project has 35 main source and resource files and 16 test files. The complete code is in the article that introduced it (the diffs show each change), and the last snapshot is the sum of articles 33 to 38.
The API
#Endpoint | Purpose |
|---|---|
| Create an account, and get an access token |
| Create an application ( |
| List with |
| Read, replace (with the optimistic-locking |
| Move to another stage ( |
| Manage interviews |
| Liveness and readiness |
Build, run and test
#You need JDK 21 or newer, Maven 3.9 and a PostgreSQL 18 server with an empty database (the Docker route is described in article 38 and was not verified here). Create the database, then supply the configuration through the environment, never through a file you commit:
export DB_URL=jdbc:postgresql://localhost:5432/jobtrackerexport DB_USER=jobtrackerexport DB_PASSWORD='your-database-password'export JWT_SECRET="$(head -c 32 /dev/urandom | base64)"mvn clean verifyjava -jar target/jobtracker-0.0.1-SNAPSHOT.jarmvn clean verify runs the 125 tests (they start their own PostgreSQL through the embedded test server, so they do not need yours) and builds the jar. Flyway creates the tables when the application starts. If you forget JWT_SECRET, the application refuses to start and tells you which variable to set.
What was verified for this course: the full test suite passes on JDK 21 and JDK 25, and the HTTP sessions in articles 33 to 38 were produced by the packaged jar on both JDKs (identical output) against a real PostgreSQL 18.6.
Requirements checklist
#Requirement | Evidence |
|---|---|
Users register and log in; passwords are hashed |
|
Every protected endpoint needs a valid token; deny by default |
|
A user can reach only their own applications |
|
Stage changes follow the rules |
|
Input is validated; errors are consistent problem-details JSON | Bean Validation on the DTOs, |
Lists are filtered and paged in the database, with a maximum page size |
|
Interviews belong to applications and are removed with them |
|
No N+1 on the list endpoint |
|
Concurrent edits cannot overwrite each other silently |
|
The schema is managed by migrations | Flyway |
Secrets are not in the code; weak or missing secrets stop start-up |
|
Health checks without leaking details | Actuator configuration, output in article 38 |
Automated tests at several levels | 125 tests, 96% line coverage |
Honest limitations
#Limitation | What a production system adds |
|---|---|
No refresh tokens, no token revocation, no login rate limiting | Refresh tokens, a deny-list or short lifetimes, throttling |
No password reset, email verification or MFA | Those features, each with its own security review |
Offset paging | Cursor paging for very large lists |
| A count in lists and a separate call for the details |
Cascading delete issues one statement per interview | A bulk delete for large sets |
The Dockerfile and Compose file were not run here | Build and test them, scan the image, pin the versions |
The migration | Add the column nullable, backfill, then make it |
One application instance was tested | Testing with several instances, and a shared rate limiter |
No end-to-end browser tests, no performance tests | Load tests and a front-end suite |
Extension ideas
#- A
GET /api/applications/summaryendpoint with counts per stage, computed in SQL. - Soft delete and an audit trail of status changes (a new table and a migration).
- Notes and attachments per application.
- Login throttling and a refresh-token flow.
- A scheduled reminder for interviews coming up, with a test using the injected
Clock. - A Testcontainers version of the database tests, if you have Docker.
Presenting it in an interview
#Be ready to explain, in your own words and with the code open:
- Why DTOs and not entities in the controllers (mass assignment, stable contracts).
- Where each rule lives and why: validation at the edge, business rules in the service, constraints in the database.
- How ownership is enforced and why other users get
404. - How you found and fixed the
lower(bytea)bug, the N+1 problem and the lagging-version bug, because real problems with real fixes are the strongest evidence of experience. - What
@Transactionaldoes, and the self-invocation and checked-exception traps. - Why optimistic locking, and what the client must send.
- How passwords and secrets are handled, and what a JWT does and does not protect.
- How you tested it, what the coverage number means, and what the mutation experiment showed.
- What you would do next, from the limitations table.
Article 41 turns these into prepared answers.
Self-review checklist
#- Can someone build and run it from a clean clone using only your README?
- Is anything secret in the repository or its history?
- Does every endpoint have a test for success, invalid input and a missing resource?
- Do you know the generated SQL for each endpoint and the number of statements?
- Can you explain every annotation in the entities and the security configuration?
- Is the README honest about what is missing?
Common mistakes in portfolio projects
#- A project nobody can run, because the README is missing or wrong.
- Committed secrets or a
.envfile. - No tests, or tests that only check mocks.
- Entities exposed as the API.
- Claims you cannot defend: "secure", "scalable", "production-ready", without evidence.
- Copying a tutorial without understanding it, which is obvious in the first follow-up question.
Exercises
#Exercise 1
Write the README
#List the sections of a README for this project that would let a stranger run it in ten minutes.
Exercise 2: Add an endpoint
#Outline, without code, the files you would change to add GET /api/applications/summary (counts per status for the caller), including the tests.
Exercise 3: Defend a decision
#An interviewer asks: "Why return 404 and not 403 for another user's application?" Answer in three sentences.
Exercise 4: Find the weakest part
#Pick the weakest part of this project from the limitations table and describe how you would fix it first, and why.
Exercise 5: What would break?
#You deploy two instances behind a load balancer. Which parts of the design still work, and which need attention?
Show solutionHide solution
What the project is, requirements (JDK, Maven, PostgreSQL), how to configure it (the environment variables, with how to generate the secret), how to build and test (mvn clean verify), how to run it, a short API table with example requests, the architecture in a few lines, the test strategy and results, the known limitations, and the licence and contact. Keep the commands copy-pasteable and test them on a clean machine.
Exercise 2
Show solutionHide solution
A repository method with a GROUP BY status query scoped to the owner (a projection record for the result), a service method that calls it and fills in zero for missing statuses, a controller method @GetMapping("/summary") taking the token, a response record, and tests at each level: a repository test on the real database, a service unit test, a controller test for the shape, and an integration test that another user's data is not counted. Check that the literal path /summary is not captured by /{id}.
Exercise 3
Show solutionHide solution
A 403 would confirm that the application exists, which leaks information and lets someone enumerate ids. 404 gives the same answer whether it does not exist or belongs to someone else, so nothing is revealed. The invariant I protect is that another user's data is never returned, and this response keeps it.
Exercise 4
Show solutionHide solution
A good first choice is login throttling plus refresh tokens, because authentication endpoints are the most attacked surface and the current tokens cannot be revoked. I would add per-account and per-address rate limiting with a test, then a short access token with a refresh token stored hashed in the database, so a stolen token has a small window. The reasoning is risk: it protects every user, and the other limitations are about scale or convenience.
Exercise 5
Show solutionHide solution
Works unchanged: the JWT approach is stateless, so any instance can verify any token, and the database handles concurrent writes with transactions and optimistic locking. Needs attention: Flyway running on every instance start (it takes a lock, but you may prefer a separate migration step), rate limiting that must be shared (an in-memory counter is per instance), the connection pool size multiplied by the number of instances against the database limit, and secrets that must be identical across instances (the signing secret).
Where to go next
#Sources & further reading
- Technology
- Software Engineering
- Career
- #Java
- #Java backend
- #Java interview
- #Java project
- #JPA
- #learn Java
- #portfolio project
- #PostgreSQL
- #REST API
- #Spring Boot
- #SQL
Connected lessons
References
- Testing Spring Boot Applications: Slice Tests, MockMvc, Real Databases and the Test Pyramid
- Testing with JUnit and Mockito: Unit Tests, Mocks and Their Limits
- Project 2: Move the Expense Tracker to PostgreSQL with JDBC
Show all 13 →
- JPA and Hibernate: Entities, the Persistence Context, Spring Data and Flyway Migrations
- HTTP, JSON and REST APIs: How Web Backends Talk
- Java Interview Concepts, Part 2: SQL, Spring, JPA, Security, Testing and the Job-Ready Checklist
- Building a REST API with Spring Boot: Controllers, Services, DTOs, Validation and Errors
- Java Interview Concepts, Part 1: Core Java, Collections, Functional Java, the JVM and Concurrency
- Spring Boot Foundations: Starters, Auto-Configuration, Properties and Profiles
- Security Fundamentals and Spring Security: Passwords, JWT, Access Control, CORS and Secrets
- Production Practices: Configuration, Logging, Docker and Health Checks for Spring Boot
- Project 1: Build an Expense Tracker CLI in Plain Java
- JPA Relationships, Lazy Loading, the N+1 Problem, Transactions and Optimistic Locking
Linked from 1 guide
Other pages in the library that point here.
Keep reading
More in Technology →Guide · Technology
Heaps and Priority Queues
Guide · Technology
Dynamic Programming Practice: From 1D to 2D
Course · Technology
DSA From Zero to Job-Ready: A Practical Guide with Java

