Java Full Stack REST API Guide: Controller, Service, Repository, DTO and Validation Layers

A Java full stack API connects the browser to business rules and stored data. A useful starting design is React request → controller → service → repository → database, followed by a response DTO travelling back to the browser. These layers separate responsibilities; they are not a requirement to create an interface and implementation for every class.

This guide uses a fictional task tracker to explain the design. The endpoint names and JSON below are illustrative API contracts, not a complete runnable application.

Controller: translate HTTP requests and responses

The controller maps a method and path, reads the request body, invokes the application operation and returns the result. Spring MVC uses annotations such as @RestController and @GetMapping for this HTTP boundary, as demonstrated in Spring’s REST service guide.

For POST /api/tasks, the controller accepts a title and due date. It should not calculate permissions from a trusted-looking browser field or put the entire workflow directly inside the request handler.

DTO: define the data allowed across the API boundary

A request DTO describes permitted input. A response DTO describes what the client can see. Keeping these separate from persistence entities makes it easier to avoid exposing internal fields or accepting fields the client should not control.

Illustrative create request:

{
  "title": "Prepare demo",
  "dueDate": "2026-10-15"
}

Illustrative response:

{
  "id": 42,
  "title": "Prepare demo",
  "status": "PENDING"
}

The server assigns the ID and initial status. For an authenticated task tracker, derive the owner from the authenticated identity rather than accepting an arbitrary owner ID in the create payload.

Validation: distinguish field errors from business rules

Check a blank title, excessive length and malformed fields at the request boundary. Spring MVC supports Bean Validation on request objects with @Valid or @Validated; the exact exception path depends on the method signature and validation configuration. See the Spring MVC validation reference.

A valid-looking field can still violate a business rule. For example, moving a cancelled task to completed may be forbidden by your application. Perform that check in the service. Keep database constraints for essential data rules too, because requests can arrive concurrently.

Service: implement the operation and its rules

The service loads the task, checks whether the current user may change it, applies the allowed state transition and saves the result. It gives the operation a meaningful name, such as completeTask. It should be possible to test a rule without constructing a browser page.

When one operation changes several related records, choose a transaction boundary that matches the intended all-or-nothing change. Verify rollback for the failures your application actually throws; checked exceptions may need explicit rollback configuration. A transaction alone does not prevent every race condition: conflicting updates may still require a uniqueness constraint, version check or suitable locking strategy.

In the usual Spring proxy-based transaction mode, a call from one method to another on the same object does not pass through the transactional proxy. Understand the configured mode before assuming an annotation has taken effect. The Spring transaction documentation explains that limitation.

Repository: persist and query the data

The repository handles storage operations such as finding a task by ID or listing tasks owned by one user. Spring Data JPA can provide implementations for repository interfaces; the official JPA guide demonstrates the basic pattern. An entity represents persisted state, while the DTO represents the API contract.

Keep SQL and data relationships understandable even when an ORM generates queries. For a list endpoint, decide sorting and pagination deliberately rather than loading every row into memory.

Choose errors that the frontend can handle

Document an error contract alongside the successful response. The following is one possible design for the task API, using the meanings in HTTP Semantics; security-sensitive applications may deliberately avoid revealing whether another user’s record exists.

  • 400: request data fails validation; identify the field and a useful correction.
  • 401 or 403: handle missing/invalid authentication separately from insufficient permission according to the security configuration.
  • 404: the requested task is unavailable under the endpoint’s visibility policy.
  • 409: the requested change conflicts with the current task state, if this is the documented API design.
  • Unexpected failure: return a controlled error; log diagnostic details on the server without exposing secrets or stack traces in the response.

Use a central exception handler where it keeps the response consistent. Return a success status only after the operation succeeds. The React screen should preserve the user’s editable input and display a clear failure message when the request is rejected.

Test the boundaries with one end-to-end example

  1. Service test: an allowed status transition succeeds; a forbidden transition fails.
  2. Controller test: a blank title produces the documented validation response.
  3. Persistence test: saved data and constraints behave correctly with the chosen database setup.
  4. Security test: changing the task ID cannot bypass ownership checks.
  5. Integration check: create a task in React, reload and verify the saved result; then simulate a failed request.

Start with one clear application and add abstractions only when they solve a real problem. For guided practice across the browser, Java API and SQL database, review Java Full Stack Development in Vizag at Softenant Technologies.

More Java Learning Resources

Continue with these focused guides, or explore Java Training in Vizag for guided practice.