Popular Searches
Popular Course Categories
Popular Courses

Creating Controllers Using Spring Annotations

Creating Controllers Using Spring Annotations

Spring MVC and Enterprise Web Development

Creating Controllers Using Spring Annotations

Course: Advanced Java Training

Topic: Creating Controllers Using Spring Annotations

Course Link: Advanced Java Training Course

Demo Link: Register for a Course Demo

Website: JustAcademy Homepage


1. Introduction to Spring Controllers

In Spring MVC, a controller is a Java class that receives HTTP requests, processes user input, communicates with the application’s service layer, and returns an appropriate response. A response may be a view such as a JSP or Thymeleaf page, or data such as JSON or XML for a REST API.

Spring provides annotations that make controller development easier by reducing the need for extensive XML configuration. These annotations identify controller classes, map URL patterns to Java methods, bind request data to method parameters, and define how responses are returned.

Why Are Controllers Important?

  • They handle incoming HTTP requests.
  • They connect the web layer with business services.
  • They extract query parameters, path variables, form fields, and request bodies.
  • They select views or return structured data.
  • They help separate presentation logic from business logic.

2. Understanding the Main Spring Controller Annotations

Annotation Purpose Typical Usage
@Controller Marks a class as an MVC controller. Web applications that return views.
@RestController Combines @Controller and @ResponseBody. REST APIs returning JSON or other response data.
@RequestMapping Maps requests to a controller or handler method. Defining URL paths and request conditions.
@GetMapping Maps HTTP GET requests. Retrieving or displaying information.
@PostMapping Maps HTTP POST requests. Creating resources or submitting forms.
@PutMapping Maps HTTP PUT requests. Replacing or updating a resource.
@PatchMapping Maps HTTP PATCH requests. Partially updating a resource.
@DeleteMapping Maps HTTP DELETE requests. Deleting a resource.
@PathVariable Reads a value from the URL path. Reading a product ID from a URL.
@RequestParam Reads a query parameter or form parameter. Filtering results or reading search terms.
@RequestBody Converts the HTTP request body into a Java object. Accepting JSON in a REST API.
@ResponseBody Writes a method return value directly to the HTTP response body. Returning JSON or plain text from an MVC controller.
@ModelAttribute Binds request parameters to an object or exposes an object to a view. Handling web forms.
@ResponseStatus Declares an HTTP status for a handler or exception. Returning a defined status such as 201 Created.

3. Creating a Controller with @Controller

The @Controller annotation identifies a class as a Spring MVC controller. Spring can discover it through component scanning when it is located in a package covered by the application's configuration.

A traditional MVC controller commonly returns a view name. Spring uses a configured view resolver to locate the corresponding page.

Example: Home Page Controller

package com.example.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/")
    public String showHomePage(Model model) {
        model.addAttribute("message", "Welcome to the Spring MVC application");
        return "home";
    }
}

Explanation:

  • @Controller registers the class as an MVC controller candidate.
  • @GetMapping("/") maps HTTP GET requests to the root URL.
  • Model carries data from the controller to the view.
  • addAttribute() makes the message available to the view.
  • return "home" identifies the view name, not a literal HTML response.

The exact view file location depends on the application's view technology and configuration. For example, a Thymeleaf application may resolve the name to a template such as templates/home.html.

4. Creating REST Controllers with @RestController

The @RestController annotation is commonly used when developing REST APIs. It combines @Controller and @ResponseBody, so the values returned by handler methods are written to the HTTP response body instead of being interpreted as view names.

Example: Basic REST Controller

package com.example.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

    @GetMapping("/api/greeting")
    public String greeting() {
        return "Hello from Spring";
    }
}

A GET request to /api/greeting returns the text Hello from Spring. For structured API responses, controllers often return Java objects or collections that Spring's HTTP message converters serialize into JSON when the appropriate converter is available.

Returning a Java Object as JSON

package com.example.web;

public class Course {
    private Long id;
    private String name;

    public Course() {
    }

    public Course(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}
package com.example.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CourseController {

    @GetMapping("/api/course")
    public Course getCourse() {
        return new Course(1L, "Advanced Java");
    }
}

With a suitable JSON message converter, such as Jackson in a typical Spring Boot web application, the response can look like this:

{
  "id": 1,
  "name": "Advanced Java"
}

The exact JSON output depends on the application's serialization configuration and the properties exposed by the object.

5. Mapping Requests with @RequestMapping

@RequestMapping can be applied at class level, method level, or both. A class-level mapping establishes a shared path, while method-level mappings define specific endpoints. It can also express conditions such as HTTP method, request parameters, headers, and supported media types.

Example: Class-Level and Method-Level Mapping

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/products")
public class ProductPageController {

    @GetMapping
    @ResponseBody
    public String listProducts() {
        return "Product list";
    }

    @PostMapping
    @ResponseBody
    public String createProduct() {
        return "Product submission received";
    }
}

Here, the class-level path is /products. The GET method handles requests to /products, while the POST method handles POST requests to the same path. The example returns plain text to keep the mapping behavior easy to understand; a production controller would usually delegate business operations to a service.

6. Using HTTP-Specific Mapping Annotations

Spring provides dedicated mapping annotations that make controller methods easier to read and maintain.

HTTP Method Annotation Common Purpose
GET @GetMapping Read or retrieve data.
POST @PostMapping Create a resource or submit data.
PUT @PutMapping Replace or update a resource.
PATCH @PatchMapping Partially modify a resource.
DELETE @DeleteMapping Delete a resource.

Choosing the correct HTTP method helps make an API predictable. For example, a GET endpoint should not normally perform a destructive action, because clients and intermediaries may repeat or cache GET requests.

7. Reading URL Values with @PathVariable

The @PathVariable annotation binds a method parameter to a variable embedded in a URL path. It is especially useful for identifying a specific resource.

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {

    @GetMapping("/api/products/{id}")
    public String getProduct(@PathVariable("id") Long productId) {
        return "Requested product ID: " + productId;
    }
}

For a request to /api/products/25, Spring converts the path value to a Long and passes it to the method. If the value cannot be converted to the required type, Spring typically responds with a client error unless the application customizes the error handling.

8. Reading Query Parameters with @RequestParam

The @RequestParam annotation reads a query parameter or a request parameter supported by the handler mapping. It is commonly used for search, filtering, pagination, and optional values.

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SearchController {

    @GetMapping("/api/search")
    public String search(@RequestParam("keyword") String keyword) {
        return "Searching for: " + keyword;
    }
}

Example request: /api/search?keyword=Spring.

Optional parameters can have a default value:

@GetMapping("/api/products")
public String listProducts(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size) {
    return "Page: " + page + ", size: " + size;
}

Default values help define predictable behavior when a client does not supply a parameter. For production APIs, validate page numbers and page sizes to prevent unreasonable requests.

9. Accepting JSON with @RequestBody

The @RequestBody annotation binds the body of an HTTP request to a Java object. It is widely used in REST APIs where clients send JSON data to create or update resources. Spring uses an appropriate HTTP message converter to deserialize the request body.

Example: Creating a Product

package com.example.web;

public class ProductRequest {
    private String name;
    private double price;

    public ProductRequest() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductApiController {

    @PostMapping("/api/products")
    public String createProduct(@RequestBody ProductRequest request) {
        return "Product received: " + request.getName();
    }
}

Example JSON request body:

{
  "name": "Keyboard",
  "price": 1499.00
}

Send the request to POST /api/products with the appropriate content type, usually Content-Type: application/json. The example demonstrates binding only; a real application should validate the input and save the product through a service and repository.

10. Handling Form Data with @ModelAttribute

@ModelAttribute is useful in traditional Spring MVC applications that process HTML forms. It can bind request parameters to an object and can also add an object to the model for rendering a view.

package com.example.web;

public class StudentForm {
    private String name;
    private String email;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class StudentController {

    @GetMapping("/students/new")
    public String showForm(Model model) {
        model.addAttribute("studentForm", new StudentForm());
        return "student-form";
    }

    @PostMapping("/students")
    public String submitForm(
            @ModelAttribute("studentForm") StudentForm form,
            Model model) {
        model.addAttribute("submittedName", form.getName());
        return "student-success";
    }
}

The GET method prepares the form page, and the POST method binds submitted form fields to the StudentForm object. The view names must correspond to templates configured in the application. Production applications should validate form values and apply appropriate security protections, including CSRF protection where applicable.

11. Returning HTTP Status Codes with @ResponseStatus

HTTP status codes communicate the outcome of an operation to the client. The @ResponseStatus annotation can declare a status code for a controller method or an exception class.

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/courses")
public class CourseApiController {

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public String createCourse(@RequestBody Course course) {
        return "Course received: " + course.getName();
    }
}

The 201 Created status indicates successful resource creation. In a production API, return the created resource or a suitable response object and ensure the resource has actually been persisted before reporting success.

12. Using ResponseEntity for Flexible Responses

ResponseEntity allows a controller to control the response body, HTTP status code, and headers. It is useful when different outcomes require different HTTP responses.

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CourseLookupController {

    @GetMapping("/api/courses/{id}")
    public ResponseEntity<String> getCourse(@PathVariable Long id) {
        if (id == null || id <= 0) {
            return ResponseEntity.badRequest()
                    .body("Invalid course ID");
        }

        return ResponseEntity.status(HttpStatus.OK)
                .body("Course ID: " + id);
    }
}

This example demonstrates response construction. Checking whether an ID is valid is not the same as checking whether a course exists. A real controller should retrieve the course through a service and return 404 Not Found when the resource does not exist.

13. Validating Request Data with @Valid

Validation prevents invalid input from entering the application's business layer. Spring supports Bean Validation annotations such as @NotBlank, @Email, @Size, and @Positive. The application needs a compatible Bean Validation implementation.

Request DTO with Validation Rules

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;

public class CourseRequest {

    @NotBlank(message = "Course name is required")
    @Size(max = 100, message = "Course name must not exceed 100 characters")
    private String name;

    @Positive(message = "Price must be greater than zero")
    private double price;

    public CourseRequest() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CourseValidationController {

    @PostMapping("/api/course-requests")
    public String submitCourse(
            @Valid @RequestBody CourseRequest request) {
        return "Valid course request received: " + request.getName();
    }
}

With validation configured, invalid input can result in a validation exception and an HTTP 400 response. The exact error structure depends on the Spring version and the application's exception-handling configuration. For older Spring applications, validation imports and dependencies may differ.

14. Handling Exceptions with @ExceptionHandler

Controllers should handle expected errors clearly instead of returning misleading success messages or exposing internal exception details. The @ExceptionHandler annotation maps specific exceptions to handler methods. For common handling across multiple controllers, use @ControllerAdvice or @RestControllerAdvice.

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestController
public class ExampleController {

    @GetMapping("/api/example")
    public String example() {
        return "Example response";
    }
}

@RestControllerAdvice
class ApiExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleInvalidArgument(
            IllegalArgumentException exception) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body("Invalid request");
    }
}

The handler returns a generic error message instead of exposing exception details. In real applications, log appropriate diagnostic information securely and provide a consistent error response format. Avoid catching every exception and treating it as a client error; unexpected server errors should be handled separately.

15. Organizing Controllers with the Service Layer

A controller should focus on HTTP-related tasks, such as accepting input, invoking business operations, and returning responses. Business rules and data access should normally reside in service and repository layers.

A common architecture is:

  1. Controller: Receives the HTTP request and validates or binds input.
  2. Service: Executes business rules and coordinates application operations.
  3. Repository: Reads and writes data using a database or another storage system.
  4. Response: Returns the result with an appropriate status code or view.

Example: Controller Calling a Service

import org.springframework.stereotype.Service;

@Service
public class GreetingService {

    public String createGreeting(String name) {
        return "Welcome, " + name;
    }
}
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingApiController {

    private final GreetingService greetingService;

    public GreetingApiController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @GetMapping("/api/welcome")
    public String welcome(
            @RequestParam(defaultValue = "Student") String name) {
        return greetingService.createGreeting(name);
    }
}

Constructor injection makes the dependency explicit and supports unit testing. Spring manages the service bean and supplies it to the controller when the application context is configured correctly.

16. Configuring and Discovering Controllers

Spring must discover controller classes and register their request mappings. In a Spring Boot application, the main application class is commonly annotated with @SpringBootApplication, which enables component scanning from its package and subpackages by default.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AdvancedJavaApplication {

    public static void main(String[] args) {
        SpringApplication.run(AdvancedJavaApplication.class, args);
    }
}

Keep controller packages within the application's component-scan scope, or configure scanning explicitly when necessary. In a traditional Spring MVC application, component scanning and MVC configuration must be set up according to the project's configuration style.

17. Controller Request Processing Flow

When a client calls a mapped endpoint, Spring MVC processes the request through several coordinated components.

  1. The client sends an HTTP request to the application.
  2. The servlet container forwards the request to the configured DispatcherServlet.
  3. Spring finds a matching handler method using request mapping information.
  4. Spring binds request values and converts them to the required Java types.
  5. Validation is applied when configured and requested by the handler signature.
  6. The controller invokes the service layer and obtains the result.
  7. Spring either resolves and renders a view or writes response data using an HTTP message converter.
  8. The servlet container sends the HTTP response to the client.

Understanding this flow makes it easier to locate problems involving incorrect URL mappings, type conversion, validation, service failures, and response serialization.

18. Common Controller Errors and Troubleshooting

Problem Possible Cause Solution
404 Not Found Incorrect URL, missing mapping, or controller not discovered. Check the endpoint path, HTTP method, component scanning, and application context path.
405 Method Not Allowed The URL exists but the requested HTTP method does not match. Use the correct method or define the intended mapping.
400 Bad Request Invalid parameter type, malformed JSON, or failed validation. Check request values, JSON structure, content type, and validation errors.
415 Unsupported Media Type The request content type is not supported by the endpoint. Send the expected content type and configure a suitable message converter.
500 Internal Server Error An unhandled exception or internal application failure. Inspect server logs and fix the underlying error without exposing sensitive details.
View name appears as text A REST controller is being used when a template view was intended. Use @Controller for view resolution or deliberately return response-body data.
Request body is null or fails to bind Incorrect JSON fields, missing setters or constructors, or incompatible types. Check the DTO, message converter, request body, and content type.
Controller bean is missing The controller package is outside the component-scan scope. Adjust package structure or configure component scanning.

19. Best Practices for Creating Spring Controllers

  • Use @Controller for view-based MVC endpoints and @RestController for APIs that return response data.
  • Prefer HTTP-specific annotations such as @GetMapping and @PostMapping for clear endpoint definitions.
  • Keep controllers small and delegate business logic to service classes.
  • Use request and response DTOs instead of exposing persistence entities directly when appropriate.
  • Validate user input using Bean Validation and appropriate business rules.
  • Return meaningful HTTP status codes and consistent error responses.
  • Use constructor injection for required dependencies.
  • Avoid placing database queries or complex business logic directly inside controller methods.
  • Apply authentication, authorization, and CSRF protections as required by the application architecture.
  • Write unit and integration tests for endpoint behavior, input validation, and error cases.
  • Document API endpoints and their expected request and response formats.

20. Practical Exercise: Build a Course Management Controller

Create a simple Spring Boot application that provides endpoints for viewing and creating course records. Start with an in-memory list for learning purposes, then extend the project with a service and database repository.

Requirements

  • Use @RestController for API endpoints.
  • Use @RequestMapping("/api/courses") as the common base path.
  • Implement GET /api/courses to retrieve courses.
  • Implement GET /api/courses/{id} to retrieve one course.
  • Implement POST /api/courses to accept a JSON course request.
  • Use @PathVariable to read a course ID.
  • Use @RequestBody and @Valid to bind and validate input.
  • Return suitable status codes for successful creation, invalid input, and missing courses.
  • Move business operations into a service class.

Example Endpoint Design

Endpoint Method Purpose
/api/courses GET Retrieve all courses.
/api/courses/1 GET Retrieve a course by ID.
/api/courses POST Create a course.
/api/courses/1 PUT Update a course.
/api/courses/1 DELETE Delete a course.

Test the endpoints using Postman, an HTTP client, or integration tests. Verify both successful and unsuccessful requests, including invalid IDs, missing fields, malformed JSON, and duplicate course data.

21. Interview Questions and Answers

Q1. What is a controller in Spring MVC?

A controller is a Spring-managed component that handles web requests, coordinates application operations, and returns a view or HTTP response.

Q2. What is the difference between @Controller and @RestController?

@Controller is commonly used for MVC controllers that return view names. @RestController combines @Controller and @ResponseBody, so handler return values are written to the response body by default.

Q3. What is the purpose of @RequestMapping?

It maps HTTP requests to controller classes or methods based on conditions such as URL path, HTTP method, headers, parameters, and media types.

Q4. Why use @GetMapping instead of @RequestMapping?

@GetMapping clearly identifies a GET endpoint. It is a convenient shortcut for a request mapping restricted to the GET method.

Q5. What is the difference between @PathVariable and @RequestParam?

@PathVariable reads a value embedded in the URL path, while @RequestParam reads a request parameter, commonly supplied in the query string or form data.

Q6. What does @RequestBody do?

It binds the HTTP request body to a Java object through an appropriate HTTP message converter.

Q7. Why is @Valid used in controller methods?

It triggers configured Bean Validation constraints on the bound object, allowing the application to reject invalid input.

Q8. What is the purpose of ResponseEntity?

It allows a controller to return a response body together with an explicit HTTP status and headers.

Q9. What is @ControllerAdvice used for?

It provides shared controller-related behavior, including centralized exception handling and model-related methods. @RestControllerAdvice is commonly used for centralized REST API error responses.

Q10. Why should business logic be placed in a service layer?

Separating business logic from HTTP handling improves maintainability, reuse, and testability while keeping controllers focused on request and response responsibilities.

22. Quick Revision Checklist

  • @Controller — MVC controller, often used for view rendering.
  • @RestController — controller whose methods return response-body data by default.
  • @RequestMapping — general request mapping for classes and methods.
  • @GetMapping, @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping — HTTP-specific endpoint mappings.
  • @PathVariable — extracts values from the URL path.
  • @RequestParam — extracts request parameters.
  • @RequestBody — binds a request body to a Java object.
  • @ModelAttribute — binds form parameters or exposes model data.
  • @Valid — triggers configured validation rules.
  • @ResponseStatus and ResponseEntity — help control HTTP response status.
  • @ExceptionHandler and @RestControllerAdvice — support centralized error handling.
  • Service classes — hold business operations separate from the controller.

23. Conclusion

Creating controllers using Spring annotations is a fundamental skill in Advanced Java development. Annotations such as @Controller, @RestController, @RequestMapping, @GetMapping, @PostMapping, @PathVariable, and @RequestBody make it possible to build clear and maintainable web applications and REST APIs.

By combining request mapping, data binding, validation, service-layer separation, and appropriate response handling, developers can create controllers that are easier to test, maintain, and extend. Practice these concepts by building a course management API and testing its successful and error scenarios.

Explore Advanced Java Training: View the Advanced Java Training Course

Register for a Demo: Register for a Course Demo

Visit JustAcademy: JustAcademy Homepage

Previous Next
whatsapp