This guide walks through the six layers needed to build a production-quality Java backend, using a money transfer app as an example. Each layer builds on the one before it, think of it like an order of operations for clean structuring. We start at the database and work our way up to the controller. By the time we reach the controller, every piece has a clear job and a clean interface.
The architecture follows standard OOP principles: separation of concerns, interface-driven design, and layered abstraction.
Step 1. Creating a Database
First we define our tables, data types, and relationships before writing any Java Logic. SQL is acting as our blueprint for the entire application. Every other part of a the application gets its building blocks from this stage.
Key Concepts:
Primary Keys - to identify each row.
Foreign Keys - to link tables together.
Password hashes - to act as additional Security.
Step 2. Creating the Model
Now that our database tables exist, we need a Java class that mirrors them. That class is called a Model. Think of it as the Java version of our table. Every column becomes a field. Every field gets a getter and a setter.
The Model class does not talk to the database, It just holds data. It is the shape our application uses to pass a user around from layer to layer.
Key Concepts:
POJO - Plain Old Java Object. A class that only holds data. No logic, no database calls.
private fields - Each column in your table becomes a private field in your class. Direct access is blocked.
Getters / Setters - The only way to read or write a field. This is encapsulation, a core OOP principle.
Big Decimal - Used for money values instead of double or float. Avoids rounding errors in financial math. It also handles much larger numbers.
Step 3. Creating a DAO interface
We have our database and we have our Model. Now we need to define how our application is going to talk to that database. That is what the DAO does. DAO stands for Data Access Object.
But before we write any actual database logic, we write an interface first. An interface is a contract. It says here are the operations that must exist. It does not say how they work. That comes in the next step.
This is one of the most important OOP principles we use. Our service layer will only ever talk to this interface. It will never care whether the data is coming from PostgreSQL, MySQL, or a test mock. That separation is what makes our application clean and easy to maintain.
Key Concepts:
DAO - Data Access Object. The layer responsible for all database communication.
interface - A contract that defines what methods must exist, without saying how they work.
program to an interface - Our service calls the interface, not the implementation. Swap the database out later without touching anything else.
CRUD - Create, Read, Update, Delete. The four fundamental database operations. Our interface covers all four.
Step 4. Creating the JDBC DAO
In Step 3 we defined the contract. Now we fulfill it. The JDBC DAO is the class that implements our UserDao interface and writes the actual SQL logic that talks to the database.
JDBC stands for Java Database Connectivity. It is the underlying technology that makes the connection between our Java application and our database possible. Think of it as the bridge. Our DAO walks across that bridge every time it needs data.
This is also where we protect ourselves from SQL injection. We use prepared statements with the ? placeholder instead of concatenating user input directly into our SQL. The database treats everything bound to a ? as pure data, never as executable code.
We also write a private helper method called mapRowToUser. Its only job is to take a row from the database result and turn it into a User object. This keeps our query methods clean and avoids repeating the same mapping logic everywhere.
Key Concepts:
JDBC - Java Database Connectivity. The driver that enables our Java app to communicate with the database.
implements - The keyword that tells Java this class is fulfilling the contract we defined in the interface.
Prepared Statement - A precompiled SQL statement that uses ? placeholders to safely bind user input.
Result Set - The object that holds the rows returned from a database query. We iterate through it to get our data.
mapRowToUser - A private helper that converts one database row into a User object. Reused by every read method.
Step 5. Creating the Service
This is the brain of our application. Everything we have built so far has been infrastructure. The database stores the data. The Model shapes it. The DAO retrieves it. The Service is where we decide what actually happens with it.
All business logic lives here. Validation, rules, calculations, and the orchestration of multiple DAO calls all happen in the Service. The Controller will call the Service. The Service will call the DAO. That order never changes.
For our money transfer app, the Service is where we ask the important questions. Does the sender exist? Does the receiver exist? Does the sender have enough money? Only after all of that passes do we touch the database.
We also use the @Transactional annotation on our transfer method. This tells the framework to wrap the entire operation in a single database transaction. If anything fails midway, everything rolls back. Money does not disappear in the middle of a transfer.
Key Concepts:
business logic - The rules of how our application behaves. Validation, calculations, and decisions all live here.
@Transactional - Wraps a method in a database transaction. All steps succeed together or none of them do.
atomicity - The guarantee that an operation is all or nothing. Critical for any financial transaction.
custom exceptions - We throw meaningful errors like InsufficientFundsException instead of letting the app crash silently.
@Service - A Spring annotation that registers this class as a service component so it can be injected elsewhere.
Step 6. Creating the Controller
We made it to the top of the stack. The Controller is the entry point for every HTTP request that comes into our application. It is what the frontend talks to. It is what we test in Postman.
The Controller has one job. Receive the request, hand it to the Service, and send back a response. That is it. No validation, no business logic, no database calls. If we find ourselves writing rules inside the Controller, they belong in the Service instead.
We use annotations to map methods to URLs. @GetMapping handles GET requests, @PostMapping handles POST requests. The @PathVariable annotation pulls a value out of the URL itself, like the {id} in /users/{id}. The @RequestBody annotation reads the JSON payload from the request body and maps it to a Java object automatically.
We also handle exceptions here. When our Service throws something like InsufficientFundsException, our exception handler catches it and turns it into a clean HTTP response with a meaningful status code instead of a raw server crash.
Key Concepts:
@RestController - Tells Spring this class handles HTTP requests and returns data directly as JSON.
@RequestMapping - Sets the base URL path for all methods in this controller. Ours is /users.
@PathVariable - Pulls a value out of the URL. The {id} in /users/{id} maps to a method parameter.
@RequestBody - Reads the incoming JSON payload and converts it into a Java object automatically.
ResponseEntity - Wraps our response so we can control the HTTP status code alongside the data we return.
@ExceptionHandler - Catches a specific exception and returns a clean HTTP error response instead of a server crash.