Core and Web-based Java

Spring Core, MVC, Boot and Data JPA

PGCP-AC

Spring organizes an application around container-managed objects and explicit collaboration. Spring MVC applies that model to web requests, Spring Boot supplies practical configuration conventions and Spring Data JPA reduces repetitive repository code. The annotations are convenient, but the important ideas are object ownership, dependency direction, request flow, transaction boundaries, configuration precedence and proxy interception.

1. Inversion of control

Without a container, a class often constructs its own dependencies:

class BookService {
    private final BookRepository repository =
            new JdbcBookRepository();
}

This couples BookService to one implementation and its construction details. In inversion of control, the Spring container creates objects and supplies their collaborators:

@Service
class BookService {
    private final BookRepository repository;

    BookService(BookRepository repository) {
        this.repository = repository;
    }
}

The container-managed objects are beans. Dependency injection is the mechanism by which required collaborators enter a bean. The class depends on a useful abstraction and can be tested with an in-memory or mock implementation.

2. Constructor, setter and field injection

Constructor injection is the preferred form for required dependencies:

  • requirements are visible in the constructor;
  • a valid object cannot be created without them;
  • fields can be final;
  • ordinary unit tests can instantiate the class;
  • excessive dependencies become visible as a design warning.

Setter injection fits optional or reconfigurable collaboration. Field injection hides requirements, prevents final fields and makes plain unit construction awkward.

Circular constructor dependencies fail because neither bean can be completed first. Introducing lazy injection may defer the problem, but a cycle usually signals responsibilities that should be separated.

3. Bean registration

Component scanning discovers stereotype-annotated classes:

  • @Component: general component;
  • @Service: application or domain service role;
  • @Repository: persistence role and related exception translation;
  • @Controller: MVC web controller;
  • @RestController: controller whose methods normally write response bodies.

Explicit configuration registers objects through factory methods:

@Configuration
class ApplicationConfig {
    @Bean
    Clock clock() {
        return Clock.systemUTC();
    }
}

Use explicit beans for third-party classes, deliberate construction logic or infrastructure. Avoid scanning an excessively broad package tree because accidental components and slow startup become harder to diagnose.

4. Resolving dependency candidates

Injection by type becomes ambiguous when several beans implement an interface:

interface MessageSender {}

@Component("emailSender")
class EmailSender implements MessageSender {}

@Component("smsSender")
class SmsSender implements MessageSender {}

@Qualifier("emailSender") identifies a candidate at the injection point. @Primary marks the default candidate when no qualifier narrows the choice. Custom qualifier annotations express intent more safely than repeated string names.

Injecting List<MessageSender> deliberately supplies all candidates, with ordering controlled by @Order or Ordered where appropriate.

5. Bean scopes

ScopeMeaning
SingletonOne instance per bean definition per container
PrototypeA new instance when requested from the container
RequestOne instance for one HTTP request
SessionOne instance for one HTTP session
ApplicationOne instance for the web application scope

Singleton does not mean one object across all JVMs or deployments. Singleton beans commonly serve concurrent requests and should be stateless or thread-safe.

The container creates prototype instances but normally does not manage their complete destruction lifecycle. Injecting a prototype directly into a singleton resolves it once during singleton creation. Use an ObjectProvider or scoped proxy when fresh lookup is genuinely required.

6. Bean lifecycle

A simplified lifecycle is:

  1. instantiate the bean;
  2. inject dependencies;
  3. invoke awareness callbacks and bean post-processors;
  4. invoke initialization callbacks;
  5. make the bean available;
  6. invoke destruction callbacks when the owning scope ends.

@PostConstruct and @PreDestroy express initialization and cleanup. @Bean(initMethod=..., destroyMethod=...) can adapt third-party types.

Do not perform slow remote work in constructors. Initialization should validate configuration and establish owned resources deliberately. Destruction cannot be guaranteed after a hard process failure.

7. ApplicationContext and BeanFactory

BeanFactory defines basic bean retrieval and dependency management. ApplicationContext extends the programming model with events, message resources, environment access, resource loading and automatic post-processor integration.

Application code should rarely call getBean repeatedly. Service-locator style hides dependencies and ties domain logic to Spring. Prefer constructor injection and reserve direct lookups for framework integration or genuinely dynamic selection.

8. Spring MVC front controller

DispatcherServlet is Spring MVC's front controller. A typical request follows:

HTTP request
  → filter chain
  → DispatcherServlet
  → HandlerMapping selects handler
  → HandlerAdapter invokes controller
  → controller calls service
  → view resolution or message conversion
  → HTTP response

HandlerMapping finds a handler for the request. A HandlerAdapter knows how to invoke it. Argument resolvers build method arguments. Return-value handlers interpret the result. Exceptions may be processed by HandlerExceptionResolver components.

9. Controller mappings

@Controller
@RequestMapping("/books")
class BookController {
    @GetMapping("/{id}")
    String show(@PathVariable long id, Model model) {
        model.addAttribute("book", service.require(id));
        return "books/show";
    }
}

@RequestMapping can constrain path, HTTP method, parameters, headers, consumed content types and produced content types. Specialized annotations include @GetMapping, @PostMapping, @PutMapping, @PatchMapping and @DeleteMapping.

Controllers should translate HTTP input into application calls, not contain transactions, SQL or large business workflows.

10. Method arguments

Common binding annotations include:

  • @PathVariable: a URI template value;
  • @RequestParam: a query or form parameter;
  • @RequestHeader: a request header;
  • @CookieValue: a cookie value;
  • @RequestBody: body converted by an HTTP message converter;
  • @ModelAttribute: form-style binding to an object.
@GetMapping
String search(@RequestParam(defaultValue = "") String title,
              Pageable pageable,
              Model model) {
    model.addAttribute("page",
            service.search(title, pageable));
    return "books/list";
}

All client input is untrusted. Binding success does not prove business validity or authorization.

11. Validation

Bean Validation constraints describe structural rules:

record CreateBookRequest(
        @NotBlank String title,
        @Positive BigDecimal price) {}
@PostMapping
String create(@Valid @ModelAttribute("form")
              CreateBookRequest form,
              BindingResult errors) {
    if (errors.hasErrors()) return "books/new";
    service.create(form);
    return "redirect:/books";
}

For form binding, BindingResult must be positioned correctly after the validated argument. Validation checks format and constraints; services still enforce business invariants and authorization. Error messages should use message codes so localized text can be selected from message resources.

12. Models and view resolution

Model stores data for rendering. A controller can return a logical view name, while a ViewResolver maps it to a template:

model.addAttribute("books", service.findAll());
return "books/list";

ModelAndView combines both explicitly. View technologies include Thymeleaf and JSP. A redirect view name begins with redirect:; a forward can use forward:, though redirection and forwarding semantics should be chosen deliberately.

Views should present prepared values and avoid business or persistence logic.

13. Response bodies and message converters

@ResponseBody tells MVC to write the return value through an HttpMessageConverter. @RestController combines @Controller and response-body semantics:

@RestController
@RequestMapping("/api/books")
class BookApiController {
    @GetMapping("/{id}")
    ResponseEntity<BookResponse> find(@PathVariable long id) {
        return ResponseEntity.ok(service.findResponse(id));
    }
}

Converters select representations from Java type, Content-Type, Accept and configuration. JSON conversion commonly uses Jackson. Do not return persistence entities directly by default: lazy relationships, cycles, internal fields and unstable contracts can leak. Return response DTOs.

14. Exception handling

@ExceptionHandler handles exceptions for a controller. @ControllerAdvice or @RestControllerAdvice centralizes handling across controllers.

@RestControllerAdvice
class ApiErrors {
    @ExceptionHandler(BookNotFoundException.class)
    ResponseEntity<ProblemDetail> notFound(
            BookNotFoundException ex) {
        return ResponseEntity
                .status(HttpStatus.NOT_FOUND)
                .body(problem(ex));
    }
}

Map domain failures to appropriate statuses and stable response shapes. Avoid returning stack traces, SQL, secrets or internal class names. Log internal causes with correlation data.

15. Multipart uploads and localization

Multipart requests carry file content and ordinary form fields. Spring binds uploaded files to MultipartFile. Validate size, declared and detected content type, filename handling and destination. Generate server-side storage names and keep untrusted files outside executable or public paths.

Spring's MessageSource resolves messages by code and Locale. Locale resolution may use request headers, cookies, sessions or application policy. Externalized messages support translated UI and validation text without hard-coding language in controllers.

16. Spring Boot

Spring Boot builds on the Spring container. It supplies:

  • starter dependency sets;
  • auto-configuration based on classpath, beans and properties;
  • embedded-server conventions;
  • external configuration;
  • production support such as Actuator when added.
@SpringBootApplication
public class LibraryApplication {
    public static void main(String[] args) {
        SpringApplication.run(
                LibraryApplication.class, args);
    }
}

@SpringBootApplication combines configuration, component scanning and auto-configuration enablement. Place it in an intentional root package so scanning covers the application without unrelated packages.

17. Auto-configuration conditions

Auto-configuration contributes beans when conditions match, for example when a class exists, a property is enabled and the application has not defined its own bean. This “back off” behaviour lets explicit application configuration replace a default.

Auto-configuration is deterministic conditional configuration, not runtime guesswork. Diagnostic reports and Actuator condition endpoints can show why a configuration matched or did not.

Excluding auto-configuration is appropriate when the application supplies a complete alternative. Broad exclusions without understanding dependencies often create fragile setup.

18. Starters and dependency management

A starter groups dependencies for a capability such as web or data access. Boot dependency management selects a tested version set, reducing manual incompatibility.

Maven describes the project in pom.xml. Its default lifecycle includes validate, compile, test, package, verify, install and deploy. Invoking package runs earlier lifecycle phases unless skipped and produces the configured artifact.

A dependency scope controls where a library is available. Plugins perform build work. The dependency tree helps diagnose conflicts and unexpected transitive libraries.

19. External configuration and profiles

Configuration may come from properties or YAML files, environment variables, system properties, command-line arguments and other configured sources. Precedence determines which value wins.

@ConfigurationProperties(prefix = "storage")
public record StorageProperties(
        Path root,
        DataSize maximumFileSize) {}

Type-safe configuration properties group related values and support validation. Profiles activate environment-specific bean definitions or document sections, but excessive profile branching makes behaviour hard to reproduce.

Keep credentials outside source control and client bundles. Use secret-management facilities and rotate compromised values.

20. Spring Data repositories

Spring Data JPA implements repository interfaces:

interface BookRepository
        extends JpaRepository<Book, Long> {
    List<Book> findByTitleContainingIgnoreCase(
            String title);
}

CrudRepository defines basic CRUD operations. PagingAndSortingRepository adds paging and sorting contracts, while JpaRepository adds JPA-oriented methods through its hierarchy.

The framework creates a proxy implementation, translates method calls into persistence operations and integrates with transactions. Repository interfaces should model useful access patterns rather than expose every possible query.

21. Derived and explicit queries

Derived queries parse method names using entity property paths:

List<Book> findByStatusAndPriceLessThan(
        Status status, BigDecimal price);

Long method names become difficult to read and maintain. @Query expresses JPQL or, when marked, native SQL:

@Query("""
       select b from Book b
       where b.author.id = :authorId
       order by b.title
       """)
List<Book> findForAuthor(
        @Param("authorId") long authorId);

Bind parameters rather than concatenating values. DTO projections, Specifications, Query by Example or custom repository implementations address more complex dynamic queries.

22. Transactions

@Transactional declares a transaction boundary:

@Service
class TransferService {
    @Transactional
    public void transfer(long from, long to,
                         BigDecimal amount) {
        accounts.debit(from, amount);
        accounts.credit(to, amount);
    }
}

Place boundaries around complete business units, usually at public service methods. By default, Spring commonly rolls back for unchecked exceptions and Error; checked-exception rollback requires deliberate configuration when needed.

Do not catch and suppress a failure inside the transaction unless success is still correct. A transaction may be marked rollback-only, causing an unexpected rollback later.

23. Propagation and isolation

Propagation defines how a method relates to an existing transaction. REQUIRED joins one or creates one and is the common default. REQUIRES_NEW suspends an outer transaction and starts an independent one. MANDATORY requires an existing transaction. Other modes define non-transactional or nested behaviour where supported.

Isolation controls concurrent database anomalies and depends on database support. Read-only is a hint and optimization opportunity, not an authorization rule.

Starting independent transactions can commit inner work even when outer work later fails. Use propagation from explicit business semantics rather than as a fix for errors.

24. AOP concepts

Aspect-oriented programming modularizes cross-cutting behaviour:

  • join point: an interceptable execution point;
  • pointcut: an expression selecting join points;
  • advice: behaviour executed at selected points;
  • aspect: a module grouping pointcuts and advice.

Advice can run before, after successful return, after throwing, after completion or around an invocation. Around advice must call proceed() when the target should execute and may transform arguments, return values or failures.

Spring uses AOP internally for transactions, security, caching, async execution and other concerns.

25. Proxy boundaries and self-invocation

Spring commonly wraps a bean in a proxy. External calls cross the proxy and can be advised:

caller → proxy → transaction advice → target method

An ordinary internal call through this stays inside the target:

public void outer() {
    this.inner(); // does not cross external proxy
}

Therefore a proxy-based annotation on inner may not apply on this path. Move the method to another bean, call through a properly injected collaborator or redesign the boundary. Avoid self-proxy tricks that hide structure.

Private or final methods and final classes can also restrict proxy interception depending on proxy mechanism. Annotation presence alone does not prove interception.

26. Layered application design

A maintainable flow is:

controller → application service → repository
     ↓              ↓                 ↓
 HTTP mapping   transaction and     persistence
 and DTOs       business rules      queries

Controllers should not open transactions or call EntityManager directly. Repositories should not decide HTTP statuses. Entities should not depend on web request objects. Clear boundaries make unit tests smaller and failures easier to locate.

DTO mapping prevents persistence implementation details from becoming public API contracts.

27. Testing Spring applications

Use the smallest useful test:

  • plain unit tests instantiate services with fakes or mocks;
  • MVC slice tests focus on controllers, mapping, validation and serialization;
  • data slice tests focus on repository mappings and queries;
  • full-context tests verify wiring and end-to-end integration.

Transactional test rollback can hide behaviour that occurs only at commit. Flush deliberately when verifying constraints or generated SQL effects. Use production-like databases for database-specific queries and isolation behaviour.

Avoid loading the entire context for every simple class test; it adds time and obscures which dependency matters.

Practical considerations

MistakeCorrect approach
Field injection everywhereUse constructors for requirements
Mutable state in singleton controllersKeep them stateless
Treating @Primary as documentation for all casesUse meaningful qualifiers
Business logic in controllersDelegate to services
Returning JPA entities directly from APIsMap to response DTOs
Assuming auto-configuration always winsExplicit beans may make it back off
Storing secrets in properties committed to GitUse protected external secrets
Very long derived query namesUse @Query, Specifications or custom code
Catching an exception inside @Transactional and reporting successPreserve rollback semantics
Expecting advice on this.method()Cross a proxy boundary or redesign

Worked request trace

For GET /api/books/7:

  1. The servlet container passes the request through filters.
  2. DispatcherServlet receives it.
  3. HandlerMapping chooses the controller method.
  4. Argument resolution converts 7 to the path-variable long.
  5. The controller invokes BookService.
  6. A transaction proxy surrounds the service method where configured.
  7. The repository proxy executes a JPA operation.
  8. The service maps the entity to a response DTO.
  9. An HTTP message converter serializes the DTO.
  10. DispatcherServlet completes the response.

If the book is missing, centralized exception handling maps the domain failure to a safe 404 body.

Continue learning

Related notes

Put this topic into timed practice

Open mock tests when you want full-exam pacing, or keep drilling in practice mode.