Skip to main content

Spring Filter Concepts: Deep Dive

Introduction

This guide provides comprehensive explanations of two fundamental concepts used in the JWT authentication system of our e-commerce application:

  1. OncePerRequestFilter - A Spring Framework class that ensures filter logic executes exactly once per HTTP request
  2. OOP Inheritance with extends - How object-oriented inheritance works, specifically analyzing JwtAuthenticationFilter extends OncePerRequestFilter

These concepts are critical for understanding how JWT authentication works in Spring Security and how the filter chain processes requests in Scenario 2: Add Item to Cart.


Part 1: Understanding OncePerRequestFilter in Spring Framework

Introduction

When building secure web applications with Spring, you'll often need to inspect and process every incoming HTTP request—checking authentication tokens, validating permissions, or logging activity. The OncePerRequestFilter class provides a reliable foundation for this critical functionality. Let's explore what it is, why it exists, and how it powers JWT authentication in modern Spring applications.


1. What is OncePerRequestFilter?

Purpose and Role

OncePerRequestFilter is an abstract base class in Spring Framework that ensures your custom filter logic executes exactly once per request. It sits in the request processing pipeline, intercepting HTTP requests before they reach your controllers.

Think of it like a security checkpoint at an airport: Every passenger (HTTP request) must pass through security (your filter) exactly once before boarding their flight (reaching the controller). You don't want passengers going through security multiple times for the same journey—it's inefficient and could cause problems.

The Problem It Solves

In servlet-based web applications, a single HTTP request can trigger filter execution multiple times due to:

  1. Request forwarding: When one servlet forwards to another using RequestDispatcher.forward()
  2. Include operations: When a servlet includes content from another resource
  3. Error handling: When error pages are processed
  4. Asynchronous processing: In async request scenarios

Without OncePerRequestFilter, you might:

  • Validate the same JWT token multiple times (performance waste)
  • Apply authentication logic repeatedly (potential bugs)
  • Create duplicate log entries (confusion in troubleshooting)
  • Encounter thread-safety issues with shared resources

Why "Once Per Request" Matters

Practical Example - JWT Authentication:

// Without OncePerRequestFilter - PROBLEMATIC
public class ProblematicJwtFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) {
// This could run 2-3 times for a single user request!
String token = extractJwtToken(request);
validateToken(token); // Expensive operation
setSecurityContext(token); // Could cause race conditions
chain.doFilter(request, response);
}
}

If this filter runs multiple times:

  • Performance hit: JWT validation involves cryptographic operations (expensive)
  • Security context pollution: Setting authentication multiple times could cause race conditions
  • Inconsistent state: The second execution might see different request attributes

OncePerRequestFilter guarantees your logic runs exactly once, eliminating these issues.


2. How OncePerRequestFilter Works

The Request Lifecycle

Let's trace a typical HTTP request through a Spring application:

Client Request

1. Servlet Container (Tomcat, Jetty)

2. Filter Chain
├── CorsFilter (if configured)
├── CharacterEncodingFilter
├── **JwtAuthenticationFilter** ← OncePerRequestFilter here
├── Other filters...
└── DispatcherServlet

3. Spring MVC
├── HandlerMapping (find controller)
├── HandlerInterceptors
└── Controller Method

4. Response flows back through the chain

Key Insight: Filters are outside the Spring MVC layer. They intercept requests before Spring even knows about them. This makes them perfect for cross-cutting concerns like authentication.

The doFilterInternal() Method

This is where you implement your custom logic:

@Override
protected void doFilterInternal(
HttpServletRequest request, // The incoming HTTP request
HttpServletResponse response, // The HTTP response you'll send back
FilterChain filterChain // Chain of remaining filters
) throws ServletException, IOException {

// Your custom logic here

// CRITICAL: Always call this to continue the chain
filterChain.doFilter(request, response);
}

Parameters Explained:

  1. HttpServletRequest:

    • Contains all request data (headers, parameters, body)
    • Provides methods like getHeader("Authorization")
    • Immutable snapshot of the current request state
  2. HttpServletResponse:

    • Your interface to send responses back
    • Set status codes, headers, write response body
    • Example: response.setStatus(HttpServletResponse.SC_UNAUTHORIZED)
  3. FilterChain:

    • Represents all remaining filters and the final servlet
    • Calling filterChain.doFilter(request, response) is MANDATORY
    • Think of it as passing the baton in a relay race

The Filter Chain Pattern

The filter chain implements the Chain of Responsibility design pattern:

// Conceptual flow
public void doFilterInternal(request, response, chain) {
// Before processing (on the way "in")
System.out.println("Filter 1: Before");

chain.doFilter(request, response); // Pass to next filter

// After processing (on the way "out")
System.out.println("Filter 1: After");
}

Execution Order:

Request arrives
→ Filter 1 (before)
→ Filter 2 (before)
→ Filter 3 (before)
→ Controller executes
← Filter 3 (after)
← Filter 2 (after)
← Filter 1 (after)
Response sent

Real-world analogy: This is like going through multiple security layers at a high-security facility:

  • Badge scan (Filter 1)
  • Metal detector (Filter 2)
  • ID verification (Filter 3)
  • Enter secure area (Controller)
  • Then exit through the same checkpoints in reverse

doFilter() vs doFilterInternal()

doFilter() (from Filter interface):

  • The public method called by the servlet container
  • Handles the "once per request" logic internally
  • You never override this in OncePerRequestFilter

doFilterInternal() (from OncePerRequestFilter):

  • Your protected implementation method
  • Only called once per request (guaranteed by the parent class)
  • Works with HttpServletRequest/Response (more convenient than generic ServletRequest/Response)
  • This is what you override
// What OncePerRequestFilter does internally (simplified)
public final void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;

// Check if already processed this request
String alreadyFilteredKey = getAlreadyFilteredAttributeName();
if (request.getAttribute(alreadyFilteredKey) != null) {
// Skip our logic, just continue the chain
chain.doFilter(request, response);
return;
}

// Mark as processed
request.setAttribute(alreadyFilteredKey, Boolean.TRUE);

// Now call YOUR logic exactly once
doFilterInternal(request, response, chain);
}

The "Aha Moment": The framework uses a request attribute as a flag. If it sees its own flag already set, it knows this request has been processed and simply passes it along without calling your doFilterInternal() again.


3. Practical Application in JWT Authentication

How It's Used in JwtAuthenticationFilter

Let's examine a typical JWT authentication filter:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

@Autowired
private JwtUtil jwtUtil;

@Autowired
private UserDetailsService userDetailsService;

@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {

// Step 1: Extract JWT token from request header
String authHeader = request.getHeader("Authorization");
String token = null;
String username = null;

if (authHeader != null && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7); // Remove "Bearer " prefix
try {
username = jwtUtil.extractUsername(token);
} catch (Exception e) {
// Token is malformed or expired
logger.error("JWT token extraction failed", e);
}
}

// Step 2: Validate token and set authentication context
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

UserDetails userDetails = userDetailsService.loadUserByUsername(username);

if (jwtUtil.validateToken(token, userDetails)) {
// Create authentication object
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);

authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);

// Set in security context - THIS IS CRITICAL
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}

// Step 3: ALWAYS continue the filter chain
filterChain.doFilter(request, response);
}
}

Why OncePerRequestFilter is Perfect for JWT

1. Performance Efficiency:

// JWT validation is expensive (cryptographic operations)
if (jwtUtil.validateToken(token, userDetails)) {
// This involves:
// - HMAC signature verification
// - Expiration date checking
// - Claims validation
// You don't want this running multiple times!
}

2. Security Context Integrity:

// Setting authentication must happen exactly once
SecurityContextHolder.getContext().setAuthentication(authToken);
// If this runs twice, you could have:
// - Race conditions in multi-threaded environments
// - Inconsistent authentication state
// - Security vulnerabilities

3. Clean Request Processing:

  • First filter execution: Extract token, validate, authenticate
  • Subsequent forwards/includes (if any): Skip authentication logic
  • Controller receives properly authenticated request exactly once

The Complete Flow

Let's trace a typical JWT-authenticated request:

1. Client Request
POST /api/cart/items
Headers: Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

2. Request enters filter chain
→ CorsFilter (handles CORS)
→ JwtAuthenticationFilter.doFilterInternal() ← OncePerRequestFilter

3. Inside doFilterInternal():
a. Extract: "Bearer eyJhbG..." → token = "eyJhbG..."
b. Decode: token → username = "user@automotive.com"
c. Validate: Check signature, expiration, claims
d. Authenticate: Create UsernamePasswordAuthenticationToken
e. Store: SecurityContextHolder.setAuthentication(authToken)
f. Continue: filterChain.doFilter(request, response)

4. Controller receives request
@PostMapping("/api/cart/items")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<CartDto> addToCart(@RequestBody AddToCartRequest request) {
// Security context is already set!
// Spring Security can verify user has USER role
String username = SecurityContextHolder.getContext()
.getAuthentication().getName();
// ... business logic
}

5. Response flows back through filters
← JwtAuthenticationFilter (no action on response)
← CorsFilter

6. Client receives response
{ "id": 1, "items": [...], "totalPrice": 299.99 }

Key Benefits in This Flow

Request Attribute Check:

// OncePerRequestFilter automatically adds this check
String filterKey = "org.springframework.web.filter.OncePerRequestFilter.FILTERED";
if (request.getAttribute(filterKey) != null) {
// Already processed - skip JWT validation
chain.doFilter(request, response);
return;
}

Thread Safety:

  • Each request has its own SecurityContext stored in a ThreadLocal
  • OncePerRequestFilter ensures single initialization per request thread
  • No race conditions even in highly concurrent environments

Error Handling:

try {
username = jwtUtil.extractUsername(token);
} catch (ExpiredJwtException e) {
// Token expired - authentication fails
// Request continues unauthenticated
// Controller's @PreAuthorize will reject it
} catch (MalformedJwtException e) {
// Invalid token format
// Same result - unauthenticated request
}
// IMPORTANT: We still call chain.doFilter()
// Let Spring Security's access control layer handle rejection

4. Key Concepts to Understand

Servlet Filters in General

What Are Servlet Filters?

Filters are components in the Java Servlet specification that intercept requests and responses. They implement the javax.servlet.Filter interface:

public interface Filter {
void init(FilterConfig config);
void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain);
void destroy();
}

Common Use Cases:

  1. Authentication/Authorization: Verify user identity (JWT, OAuth, session-based)
  2. Logging: Record request details, response times, errors
  3. Compression: Gzip response bodies
  4. CORS: Handle Cross-Origin Resource Sharing headers
  5. Character Encoding: Ensure consistent encoding (UTF-8)
  6. Request/Response Modification: Add headers, sanitize inputs

Spring's Filter Registration:

@Configuration
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.addFilterBefore(
jwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class
);
return http.build();
}

@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
}

The Filter Chain Pattern

Design Pattern Fundamentals:

The Filter Chain is an implementation of the Chain of Responsibility pattern. Each filter:

  1. Can process the request
  2. Decides whether to pass to the next filter
  3. Can process the response on the way back

Visualization:

Request  →  [Filter 1] → [Filter 2] → [Filter 3] → [Servlet/Controller]
↓ before ↓ before ↓ before ↓ process
↑ after ↑ after ↑ after
Response ← [Filter 1] ← [Filter 2] ← [Filter 3] ← [Response]

Code Example:

public class LoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {

long startTime = System.currentTimeMillis();
String path = request.getRequestURI();

logger.info("Request started: {} {}", request.getMethod(), path);

try {
// Continue the chain
chain.doFilter(request, response);
} finally {
// This runs AFTER the controller and subsequent filters
long duration = System.currentTimeMillis() - startTime;
logger.info("Request completed: {} {} - {}ms - Status: {}",
request.getMethod(), path, duration, response.getStatus());
}
}
}

Critical Rule: Always call chain.doFilter() unless you intentionally want to short-circuit the request:

// Short-circuit example - reject request early
if (!isValidApiKey(request)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid API key");
return; // Stop here - don't call chain.doFilter()
}

Request/Response Wrapping

Why Wrap?

Sometimes you need to modify request/response data, but the original objects are immutable or difficult to change. Wrapper classes solve this:

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedBody;

public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
// Cache the body so it can be read multiple times
InputStream inputStream = request.getInputStream();
this.cachedBody = StreamUtils.copyToByteArray(inputStream);
}

@Override
public ServletInputStream getInputStream() {
return new CachedBodyServletInputStream(this.cachedBody);
}

@Override
public BufferedReader getReader() {
return new BufferedReader(
new InputStreamReader(new ByteArrayInputStream(this.cachedBody))
);
}
}

Use Case in JWT Filter:

@Override
protected void doFilterInternal(HttpServletRequest request, ...) {
// Problem: Request body can only be read once
// If you read it here for validation, controller can't read it

// Solution: Wrap the request
CachedBodyHttpServletRequest wrappedRequest =
new CachedBodyHttpServletRequest(request);

// Now both filter and controller can read the body
String body = readBody(wrappedRequest);
validateRequestBody(body);

// Pass wrapped request to chain
chain.doFilter(wrappedRequest, response);
}

Thread Safety Considerations

The ThreadLocal Pattern:

Spring Security uses ThreadLocal to store security context per thread:

// Simplified version of how SecurityContextHolder works
public class SecurityContextHolder {
private static final ThreadLocal<SecurityContext> contextHolder =
new ThreadLocal<>();

public static SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}

public static void clearContext() {
contextHolder.remove();
}
}

Why This Matters for OncePerRequestFilter:

  1. Request Isolation: Each HTTP request is handled by a separate thread
  2. No Shared State: Authentication in one request doesn't affect others
  3. Automatic Cleanup: Thread pool reuses threads, but ThreadLocal is cleared between requests

Thread Safety Example:

@Override
protected void doFilterInternal(...) {
// SAFE: Each thread gets its own SecurityContext
SecurityContextHolder.getContext().setAuthentication(authToken);

// UNSAFE: Shared instance variable (DON'T DO THIS)
// this.currentUser = username; // Would leak across requests!

chain.doFilter(request, response);

// Optional: Explicit cleanup (Spring does this automatically)
// SecurityContextHolder.clearContext();
}

Concurrency Scenario:

Thread 1: User A requests /api/cart
Thread 2: User B requests /api/products
Thread 3: User C requests /api/orders

Each thread:
1. Gets its own SecurityContext from ThreadLocal
2. Sets authentication independently
3. Processes request without interfering with others
4. Clears context after response

Common Mistake:

// DON'T DO THIS - Instance variables are shared across threads!
public class BadJwtFilter extends OncePerRequestFilter {
private String currentUsername; // DANGER: Shared across all requests!

@Override
protected void doFilterInternal(...) {
this.currentUsername = extractUsername(token); // Race condition!
// Thread 1 sets "userA", Thread 2 overwrites with "userB"
// Thread 1 might now see "userB" instead of "userA"!
}
}

Correct Approach:

// DO THIS - Use local variables or ThreadLocal storage
public class GoodJwtFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(...) {
String username = extractUsername(token); // Local variable - thread-safe
// ... use username only within this method
SecurityContextHolder.getContext().setAuthentication(auth); // ThreadLocal - safe
}
}

5. Common Pitfalls and Best Practices

When NOT to Use OncePerRequestFilter

1. Conditional Filtering Based on URL Patterns:

// BAD: Filtering every request and checking inside
public class BadApiKeyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(...) {
if (request.getRequestURI().startsWith("/api/public")) {
// Don't validate API key for public endpoints
chain.doFilter(request, response);
return;
}
// Validate API key...
}
}

Better Approach - Use shouldNotFilter():

// GOOD: Skip filter entirely for excluded paths
public class GoodApiKeyFilter extends OncePerRequestFilter {

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
return path.startsWith("/api/public") ||
path.startsWith("/h2-console") ||
path.equals("/api/auth/login");
}

@Override
protected void doFilterInternal(...) {
// Only runs for paths that need API key validation
validateApiKey(request);
chain.doFilter(request, response);
}
}

Why This Matters:

  • Performance: Avoids unnecessary filter initialization and execution
  • Clarity: Explicit about which paths are filtered
  • Maintainability: Centralized path exclusion logic

2. Response Modification Only:

// If you only modify responses, consider using ResponseBodyAdvice
@RestControllerAdvice
public class ResponseWrapper implements ResponseBodyAdvice<Object> {
@Override
public Object beforeBodyWrite(Object body, ...) {
// Wrap all responses in standard format
return new ApiResponse<>(body, "success", LocalDateTime.now());
}
}

3. Business Logic:

// WRONG: Don't put business logic in filters
public class BadOrderFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(...) {
// DON'T DO THIS - business logic belongs in service layer
Order order = orderService.createOrder(request.getParameter("userId"));
orderService.applyDiscount(order);
chain.doFilter(request, response);
}
}

Filters should handle cross-cutting concerns (authentication, logging, CORS), not business operations.

Performance Considerations

1. Minimize Work in Filters:

// BAD: Heavy operations on every request
public class SlowFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(...) {
// DON'T: Database call on every request
User user = userRepository.findByUsername(username);

// DON'T: External API call
boolean isBlacklisted = externalApiService.checkBlacklist(user.getEmail());

chain.doFilter(request, response);
}
}

GOOD: Cache and optimize:

public class OptimizedFilter extends OncePerRequestFilter {

@Cacheable(value = "userCache", key = "#username")
private UserDetails loadUserDetails(String username) {
return userDetailsService.loadUserByUsername(username);
}

@Override
protected void doFilterInternal(...) {
String username = jwtUtil.extractUsername(token);

// Cached - fast on repeated requests
UserDetails user = loadUserDetails(username);

chain.doFilter(request, response);
}
}

2. Avoid Blocking Operations:

// BAD: Synchronous external call
String result = restTemplate.getForObject("https://api.external.com/validate", String.class);

// BETTER: Use timeouts
RestTemplate template = new RestTemplate();
template.setRequestFactory(new HttpComponentsClientHttpRequestFactory() {{
setConnectTimeout(2000); // 2 second timeout
setReadTimeout(2000);
}});

3. Filter Order Matters:

@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// Fast filters first
.addFilterBefore(corsFilter, ChannelProcessingFilter.class)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
// Slower filters later (if needed)
.addFilterAfter(auditFilter, FilterSecurityInterceptor.class);
return http.build();
}
}

Rationale: If CORS validation fails (fast check), you skip JWT validation (slower crypto operations).

Error Handling in Filters

Challenge: Exceptions in filters are outside Spring MVC, so @ControllerAdvice doesn't catch them.

Solution 1: Handle Explicitly:

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
try {
String token = extractToken(request);
String username = jwtUtil.extractUsername(token);

if (username != null) {
UserDetails user = userDetailsService.loadUserByUsername(username);

if (jwtUtil.validateToken(token, user)) {
setAuthentication(user, request);
}
}

chain.doFilter(request, response);

} catch (ExpiredJwtException e) {
logger.warn("JWT token expired: {}", e.getMessage());
sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED,
"Token expired");
} catch (MalformedJwtException e) {
logger.error("Invalid JWT token: {}", e.getMessage());
sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST,
"Invalid token format");
} catch (Exception e) {
logger.error("Authentication error: ", e);
sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Authentication failed");
}
}

private void sendErrorResponse(HttpServletResponse response, int status, String message)
throws IOException {
response.setStatus(status);
response.setContentType("application/json");
response.getWriter().write(
String.format("{\"error\": \"%s\", \"status\": %d}", message, status)
);
}

Solution 2: Delegate to Spring's Error Handling:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

@Autowired
private HandlerExceptionResolver resolver;

@Override
protected void doFilterInternal(...) {
try {
// Authentication logic
chain.doFilter(request, response);
} catch (Exception e) {
// Let Spring's exception resolver handle it
resolver.resolveException(request, response, null, e);
}
}
}

Solution 3: Let It Pass Through (Recommended for JWT):

@Override
protected void doFilterInternal(...) {
try {
String token = extractToken(request);
// ... validate and authenticate
} catch (Exception e) {
// Log but don't throw - let request continue unauthenticated
logger.warn("JWT validation failed: {}", e.getMessage());
// SecurityContextHolder remains empty
}

// Always continue chain
chain.doFilter(request, response);
// Controller's @PreAuthorize will reject unauthenticated requests
}

Why This Works:

@RestController
@RequestMapping("/api/cart")
public class CartController {

@PostMapping("/items")
@PreAuthorize("hasRole('USER')") // This annotation checks authentication
public ResponseEntity<CartDto> addToCart(@RequestBody AddToCartRequest request) {
// If JWT filter failed to authenticate, this throws AccessDeniedException
// Spring Security's exception handler converts to 403 Forbidden response
}
}

Best Practices Summary

✅ DO:

  1. Use shouldNotFilter() for path exclusions:

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
    return new AntPathMatcher().match("/api/public/**", request.getRequestURI());
    }
  2. Always call chain.doFilter() (unless intentionally short-circuiting):

    // Even if authentication fails, continue the chain
    chain.doFilter(request, response);
  3. Use local variables, not instance variables:

    String username = extract(token); // Thread-safe local variable
  4. Log errors but handle gracefully:

    catch (Exception e) {
    logger.error("Filter error", e);
    // Decide: respond with error or continue chain
    }
  5. Leverage shouldNotFilterErrorDispatch() and shouldNotFilterAsyncDispatch():

    @Override
    protected boolean shouldNotFilterErrorDispatch() {
    return true; // Don't re-filter error pages
    }

❌ DON'T:

  1. Don't put business logic in filters (use services)
  2. Don't forget to call chain.doFilter() (request will hang)
  3. Don't use instance variables (thread-safety issues)
  4. Don't perform heavy operations (database calls, external APIs without caching)
  5. Don't assume @ControllerAdvice will catch filter exceptions (it won't)

Conclusion: Bringing It All Together

The Mental Model

Think of OncePerRequestFilter as a smart checkpoint system:

  1. Checkpoint (Filter): Validates credentials exactly once
  2. Tracking (Request Attribute): Stamps the request so other checkpoints know it's been validated
  3. Pass Through (Filter Chain): Allows the request to proceed to its destination
  4. Return Journey (Response): Request flows back through the same checkpoints

Key Takeaways

For JWT Authentication:

  • OncePerRequestFilter ensures JWT validation happens exactly once per request
  • It prevents performance waste and thread-safety issues
  • It integrates seamlessly with Spring Security's context management

For Spring Development:

  • Filters operate before Spring MVC (perfect for security)
  • The filter chain pattern enables modular, composable request processing
  • Thread safety is handled via ThreadLocal (SecurityContext)

For Code Quality:

  • Keep filters focused on cross-cutting concerns
  • Use shouldNotFilter() for path-based filtering
  • Handle errors explicitly—don't rely on @ControllerAdvice
  • Optimize for performance—filters run on EVERY request

The "Aha Moment"

The brilliance of OncePerRequestFilter isn't just the "once per request" guarantee—it's that it makes correct behavior the default. Without it, developers would need to:

  • Manually track request processing state
  • Worry about forwarding and inclusion scenarios
  • Deal with thread-safety in complex ways
  • Write boilerplate for every filter

OncePerRequestFilter handles all this complexity, letting you focus on what your filter does, not when or how often it runs.


Further Exploration

Related Spring Concepts:

  • Interceptors (HandlerInterceptor): Similar to filters but operate within Spring MVC (after DispatcherServlet)
  • Aspect-Oriented Programming (AOP): Cross-cutting concerns at the method level
  • SecurityContextPersistenceFilter: Works with OncePerRequestFilter to manage security context lifecycle
  • DelegatingFilterProxy: Bridge between servlet filters and Spring beans

When to Use What:

ConcernUse FilterUse InterceptorUse AOP
Authentication✅ Yes❌ No (too late)❌ No
Logging✅ Yes✅ Yes✅ Yes
CORS✅ Yes❌ No❌ No
Controller-specific logic❌ No✅ Yes✅ Yes
Transaction management❌ No❌ No✅ Yes

Recommended Reading:

  • Spring Security Architecture documentation
  • Servlet Filter specification (JSR 340)
  • Spring Web MVC request processing lifecycle
  • ThreadLocal pattern in Java concurrency

By understanding OncePerRequestFilter, you've gained insight into one of the foundational patterns in Spring Security. This knowledge applies not just to JWT authentication, but to any cross-cutting concern in web applications. The pattern's elegance lies in its simplicity: ensure critical logic runs exactly once, efficiently and safely.


Part 2: Understanding OOP Inheritance - The extends Keyword

Introduction

Object-oriented programming's inheritance mechanism is one of the most powerful tools for code reuse and polymorphism. In this section, we'll deeply analyze how the extends keyword works by examining a real-world example from our Spring Boot e-commerce application:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
// Implementation details...
}

This single line of code demonstrates multiple OOP principles working together. Let's unpack it all.


1. OOP Inheritance Fundamentals

What "extends" Means in Object-Oriented Programming

The extends keyword establishes an inheritance relationship between two classes:

public class Child extends Parent {
// Child inherits from Parent
}

Key concepts:

  • The parent class (also called superclass or base class) provides functionality
  • The child class (also called subclass or derived class) inherits that functionality
  • The child can use, override, or extend the parent's behavior

The IS-A Relationship

Inheritance creates an "IS-A" relationship:

public class JwtAuthenticationFilter extends OncePerRequestFilter {
// JwtAuthenticationFilter IS-A OncePerRequestFilter
// JwtAuthenticationFilter IS-A GenericFilterBean (grandparent)
// JwtAuthenticationFilter IS-A Filter (great-grandparent interface)
}

This means:

  • Wherever OncePerRequestFilter is expected, you can use JwtAuthenticationFilter
  • Spring can treat it as a Filter without knowing the specific type
  • The relationship is transitive (inherits from entire chain)

Parent Class vs Child Class Terminology

Parent Class (Superclass, Base Class):

public abstract class OncePerRequestFilter {
// Provides common functionality
// Defines template for children
}

Child Class (Subclass, Derived Class):

public class JwtAuthenticationFilter extends OncePerRequestFilter {
// Specializes parent behavior
// Adds JWT-specific logic
}

Why We Use Inheritance

1. Code Reuse:

// Without inheritance - repeat for every filter
public class JwtFilter implements Filter {
public void doFilter(...) {
// 100 lines of once-per-request logic
// Then JWT validation logic
}
}

public class LoggingFilter implements Filter {
public void doFilter(...) {
// Same 100 lines of once-per-request logic
// Then logging logic
}
}

With inheritance - write once:

public class JwtFilter extends OncePerRequestFilter {
protected void doFilterInternal(...) {
// Only JWT validation logic
}
}

public class LoggingFilter extends OncePerRequestFilter {
protected void doFilterInternal(...) {
// Only logging logic
}
}

2. Polymorphism:

List<Filter> filters = Arrays.asList(
new JwtAuthenticationFilter(),
new CorsFilter(),
new LoggingFilter()
);

for (Filter filter : filters) {
filter.doFilter(request, response, chain); // Works for all!
}

3. Template Method Pattern:

// Parent defines algorithm structure
public abstract class OncePerRequestFilter {
public final void doFilter(...) {
// Step 1: Check if filtered (FIXED)
// Step 2: Mark as filtered (FIXED)
// Step 3: doFilterInternal() (VARIES - children implement)
// Step 4: Continue chain (FIXED)
}
}

2. What Happens When a Class Extends Another

What the Child Class Inherits

Inherited Members:

public abstract class OncePerRequestFilter {
// Child inherits these:

// Public methods - fully accessible
public final void doFilter(...) { }

// Protected methods - accessible and overridable
protected boolean shouldNotFilter(HttpServletRequest request) { }
protected String getAlreadyFilteredAttributeName() { }

// Private fields/methods - exist but NOT accessible
private static final String SUFFIX = ".FILTERED";

// Abstract methods - MUST be implemented
protected abstract void doFilterInternal(...);
}

Visualization:

OncePerRequestFilter (Parent)
├── public doFilter() [final] → Child can USE (inherited)
├── protected shouldNotFilter() → Child can USE or OVERRIDE
├── protected getAlreadyFilteredAttributeName() → Child can USE
├── private SUFFIX → Child CANNOT access directly
└── abstract doFilterInternal() → Child MUST implement

JwtAuthenticationFilter (Child)
├── Inherited: doFilter()
├── Inherited: shouldNotFilter()
├── Inherited: getAlreadyFilteredAttributeName()
├── Implemented: doFilterInternal() → Child's implementation
└── New: private JwtUtil jwtUtil → Child's own field

Method Overriding vs Method Overloading

Method Overriding (same signature, different implementation):

// Parent
protected boolean shouldNotFilter(HttpServletRequest request) {
return false; // Default: filter all requests
}

// Child overrides
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getServletPath();
return path.equals("/api/auth/login"); // Skip login endpoint
}

Method Overloading (same name, different parameters):

public class MathUtils {
// Overloaded methods (NOT overriding)
public int add(int a, int b) { return a + b; }
public double add(double a, double b) { return a + b; }
public int add(int a, int b, int c) { return a + b + c; }
}

Key Differences:

AspectOverridingOverloading
SignatureSameDifferent parameters
RelationshipParent-childSame class
Keyword@Override annotationNot applicable
PolymorphismRuntime (dynamic)Compile-time (static)

The Role of Abstract Classes and Abstract Methods

Abstract Class:

  • Cannot be instantiated directly
  • Can contain both implemented and abstract methods
  • Serves as a template for subclasses
// Abstract class
public abstract class OncePerRequestFilter {

// Concrete method (implemented)
public final void doFilter(...) {
// Implementation provided
}

// Abstract method (must be implemented by children)
protected abstract void doFilterInternal(...);
}

// Cannot do this:
OncePerRequestFilter filter = new OncePerRequestFilter(); // ❌ Compilation error

// Must do this:
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(); // ✓ OK

Abstract Methods:

// Parent declares contract
protected abstract void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain
) throws ServletException, IOException;

// Child MUST implement
@Override
protected void doFilterInternal(...) {
// Child's specific implementation
}

If you don't implement abstract methods:

// This won't compile:
public class IncompleteFilter extends OncePerRequestFilter {
// Error: Must implement doFilterInternal()
}

// Solution 1: Implement the method
public class CompleteFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(...) {
// Implementation
}
}

// Solution 2: Make your class abstract too
public abstract class PartialFilter extends OncePerRequestFilter {
// OK to not implement if this class is also abstract
}

How super() and super.method() Work

super() - Call parent constructor:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

private JwtUtil jwtUtil;

public JwtAuthenticationFilter(JwtUtil jwtUtil) {
super(); // Call OncePerRequestFilter's constructor (implicit if not specified)
this.jwtUtil = jwtUtil;
}
}

Constructor Chaining:

new JwtAuthenticationFilter(jwtUtil)

1. GenericFilterBean() constructor runs

2. OncePerRequestFilter() constructor runs

3. JwtAuthenticationFilter(jwtUtil) constructor runs

Object fully initialized

super.method() - Call parent method:

@Override
protected void doFilterInternal(...) {
// Your preprocessing
log.debug("JWT Filter: Processing request");

// Call parent's implementation (if it weren't abstract)
// super.doFilterInternal(request, response, chain);

// Or call other parent methods
String attributeName = super.getAlreadyFilteredAttributeName();
log.debug("Filter attribute: {}", attributeName);

chain.doFilter(request, response);
}

Why super() must be first:

public JwtAuthenticationFilter(JwtUtil jwtUtil) {
// Can't use 'this' before super() is called
// this.jwtUtil = jwtUtil; // ❌ Error

super(); // Must initialize parent first

this.jwtUtil = jwtUtil; // ✓ Now OK
}

3. Specific Analysis of JwtAuthenticationFilter extends OncePerRequestFilter

What JwtAuthenticationFilter Inherits from OncePerRequestFilter

Inherited from OncePerRequestFilter:
├── doFilter() method (public final)
│ ├── Checks if request already filtered
│ ├── Marks request as filtered
│ ├── Calls doFilterInternal() ← YOUR implementation
│ └── Handles async/error dispatches

├── shouldNotFilter() method (protected)
│ └── Determines if filter should skip this request

├── shouldNotFilterAsyncDispatch() method (protected)
│ └── Determines if filter should skip async dispatches

├── shouldNotFilterErrorDispatch() method (protected)
│ └── Determines if filter should skip error dispatches

└── getAlreadyFilteredAttributeName() method (protected)
└── Returns the attribute name used to track filtering

Which Methods it MUST Override

Required: doFilterInternal()

@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
// This is ABSTRACT in OncePerRequestFilter
// You MUST implement it or your class must also be abstract
}

Why it's required:

// OncePerRequestFilter says:
abstract class OncePerRequestFilter extends GenericFilterBean {
// I know HOW to ensure once-per-request execution
public final void doFilter(...) {
// My logic here
doFilterInternal(request, response, filterChain); // But what happens here?
}

// You tell me WHAT to do with each request:
protected abstract void doFilterInternal(...); // Abstract = you must implement
}

Which Methods it CAN Use (Optional Overrides)

Optional: shouldNotFilter()

// You COULD override this if you wanted:
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
// Skip JWT validation for public endpoints
String path = request.getServletPath();
return path.equals("/api/auth/login") ||
path.equals("/api/auth/register") ||
path.startsWith("/api/products"); // Public product browsing
}

Why this is useful:

  • Performance: Don't waste time validating JWT for public endpoints
  • Correctness: Login/register endpoints shouldn't require authentication
  • Clarity: Better than checking inside doFilterInternal()

How the @Component Annotation Fits In

@Component  // ← Spring-specific annotation
public class JwtAuthenticationFilter extends OncePerRequestFilter {

What @Component does:

Without @Component:
├── Class exists in your code
├── You must manually instantiate it
└── Spring doesn't know about it

With @Component:
├── Spring automatically creates an instance (bean)
├── Spring manages its lifecycle
├── Spring can inject dependencies (@Autowired)
└── Spring can use it in the security filter chain

Real-world effect:

// Without @Component - you'd have to do this:
@Configuration
public class SecurityConfig {
@Bean
public JwtAuthenticationFilter jwtFilter(JwtUtil jwtUtil, UserService userService) {
return new JwtAuthenticationFilter(jwtUtil, userService);
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
}

// With @Component - Spring handles it:
@Configuration
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtFilter; // Spring auto-creates and injects

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}

The Contract/Template Pattern at Play

Template Method Pattern = Parent defines the algorithm structure, child fills in specific steps

// OncePerRequestFilter (Parent) - THE TEMPLATE:
public abstract class OncePerRequestFilter extends GenericFilterBean {

public final void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) {
// STEP 1: Convert to HTTP types (Spring does this)
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;

// STEP 2: Check if already filtered (Spring does this)
String alreadyFilteredAttribute = getAlreadyFilteredAttributeName();
if (request.getAttribute(alreadyFilteredAttribute) != null) {
chain.doFilter(request, response); // Skip, already done
return;
}

// STEP 3: Mark as filtered (Spring does this)
request.setAttribute(alreadyFilteredAttribute, Boolean.TRUE);

// STEP 4: Do the actual filtering (YOU do this)
doFilterInternal(httpRequest, httpResponse, chain);

// The structure is fixed (final method)
// But the behavior varies (abstract method you implement)
}

protected abstract void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain
) throws ServletException, IOException;
}
// JwtAuthenticationFilter (Child) - FILLS IN THE TEMPLATE:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
// YOUR specific implementation of the abstract step:

// 1. Extract JWT token
String jwtToken = extractToken(request);

// 2. Validate token
if (isValidToken(jwtToken)) {
// 3. Set authentication
setAuthentication(jwtToken);
}

// 4. Continue the chain
filterChain.doFilter(request, response);
}
}

Visual representation:

┌────────────────────────────────────────┐
│ OncePerRequestFilter Template │
├────────────────────────────────────────┤
│ doFilter() [FINAL - Can't override] │
│ 1. Check if filtered [FIXED] │
│ 2. Mark as filtered [FIXED] │
│ 3. doFilterInternal() [VARIES]◄───┼── You implement this
│ 4. Continue chain [FIXED] │
└────────────────────────────────────────┘

4. Practical Implications in Spring

How Spring Creates and Manages This Filter

The Bean Lifecycle:

Application Startup

1. Component Scanning
Spring finds: @Component JwtAuthenticationFilter

2. Bean Instantiation
Spring calls: new JwtAuthenticationFilter()
(Uses reflection to create instance)

3. Dependency Injection
Spring sees: @Autowired JwtUtil jwtUtil
Spring sees: @Autowired UserService userService
Spring injects those beans

4. Registration
Spring adds filter to security filter chain

5. Ready for Requests
Filter is now active and watching all requests

Important: Spring creates ONE instance (singleton) and reuses it for all requests.

The Execution Flow When a Request Comes In

Let's trace a real request through your system:

User makes request: GET /api/cart
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

┌─────────────────────────────────────────────────────┐
│ Spring Security Filter Chain │
├─────────────────────────────────────────────────────┤
│ 1. SecurityContextPersistenceFilter │
│ (Loads existing authentication if any) │
│ ↓ │
│ 2. JwtAuthenticationFilter (YOUR FILTER) │
│ ↓ │
│ OncePerRequestFilter.doFilter() called │
│ ↓ │
│ Is request already filtered? No │
│ ↓ │
│ Mark request as filtered │
│ ↓ │
│ Call doFilterInternal() ← YOUR CODE RUNS │
│ ├─ Extract header: "Bearer eyJ..." │
│ ├─ Parse token: "eyJ..." │
│ ├─ Extract username: "user@automotive.com" │
│ ├─ Load user details │
│ ├─ Validate token │
│ ├─ Create authentication token │
│ └─ Set in SecurityContextHolder │
│ ↓ │
│ filterChain.doFilter() - continue to next filter │
│ ↓ │
│ 3. UsernamePasswordAuthenticationFilter │
│ (Skips, already authenticated) │
│ ↓ │
│ 4. ExceptionTranslationFilter │
│ ↓ │
│ 5. FilterSecurityInterceptor │
│ (Checks if user has permission) │
│ ↓ │
└─────────────────────────────────────────────────────┘

CartController.getCart() executes

Authentication is available:
SecurityContextHolder.getContext().getAuthentication()

Returns cart for authenticated user

Key insight: Your doFilterInternal() method is called automatically by Spring for every HTTP request, but OncePerRequestFilter ensures it only runs once per request even if the request passes through multiple dispatches.

Why OncePerRequestFilter Was Chosen as the Parent Class

Problem it solves:

// Without OncePerRequestFilter:
public class JwtAuthenticationFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) {
// This could be called MULTIPLE times for the same request:
// 1. Initial request
// 2. Forward to another servlet
// 3. Include another resource
// 4. Error handling dispatch

// Each time, you'd extract and validate the JWT again!
// Wasteful and could cause unexpected behavior
}
}

With OncePerRequestFilter:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(...) {
// Guaranteed to run EXACTLY ONCE per request
// Even if request is forwarded, included, or error-dispatched

// Safe to do expensive operations:
UserDetails userDetails = userService.loadUserByUsername(username);
// ↑ Database query - you don't want this multiple times!
}
}

Real scenario where this matters:

// Request flow:
1. GET /api/admin/products

2. JwtAuthenticationFilter validates JWT

3. Controller throws AccessDeniedException

4. Spring forwards to error handler

5. WITHOUT OncePerRequestFilter:
- JwtAuthenticationFilter runs AGAIN
- Database query AGAIN
- Token validation AGAIN

WITH OncePerRequestFilter:
- Skips (already filtered)
- Uses existing authentication

Alternative Approaches and Why They're Less Suitable

Alternative 1: Implement Filter directly

@Component
public class JwtAuthenticationFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) { }

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) {
// YOU must:
// - Cast to HttpServletRequest/HttpServletResponse
// - Track if already filtered (write that logic yourself)
// - Handle async dispatches
// - Handle error dispatches
// - Handle all edge cases

// 50+ lines of boilerplate before your actual JWT logic!
}

@Override
public void destroy() { }
}

Why it's worse:

  • ❌ Massive boilerplate code
  • ❌ Easy to miss edge cases
  • ❌ Could run multiple times per request
  • ❌ Must handle HTTP casting manually

Alternative 2: Implement GenericFilterBean

@Component
public class JwtAuthenticationFilter extends GenericFilterBean {

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) {
// Better than raw Filter, but still:
// - No once-per-request guarantee
// - Must cast to HTTP types
// - Must track filtering yourself
}
}

Why it's worse:

  • ⚠️ Still could run multiple times
  • ⚠️ Still need HTTP casting
  • ⚠️ Missing Spring Security integration helpers

Alternative 3: Use @WebFilter annotation

@WebFilter(urlPatterns = "/*")
public class JwtAuthenticationFilter implements Filter {
// Problem: This is Servlet API, not Spring-managed
// - No dependency injection
// - No access to Spring beans
// - Harder to test
// - Doesn't integrate with Spring Security
}

Why OncePerRequestFilter is optimal:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
// ✓ Spring-managed bean (dependency injection)
// ✓ Runs exactly once per request
// ✓ HTTP types built-in (no casting)
// ✓ Can skip specific dispatches
// ✓ Template method pattern (focus on your logic)
// ✓ Integrates seamlessly with Spring Security
// ✓ Easy to test
// ✓ Minimal boilerplate
}

5. OOP Principles Demonstrated

Single Responsibility Principle (SRP)

Definition: A class should have only one reason to change.

// OncePerRequestFilter's responsibility:
// "Ensure a filter executes exactly once per request"
public abstract class OncePerRequestFilter {
// Only changes if:
// - Once-per-request logic needs updating
// - Servlet API changes
}

// JwtAuthenticationFilter's responsibility:
// "Validate JWT tokens and set authentication"
public class JwtAuthenticationFilter extends OncePerRequestFilter {
// Only changes if:
// - JWT validation logic changes
// - Authentication requirements change
}

Without inheritance (violates SRP):

public class JwtAuthenticationFilter implements Filter {
public void doFilter(...) {
// Responsibility 1: Ensure once-per-request
if (request.getAttribute("ALREADY_FILTERED") != null) return;
request.setAttribute("ALREADY_FILTERED", true);

// Responsibility 2: Validate JWT
String token = extractToken(request);
validateToken(token);

// TWO reasons to change in ONE class!
}
}

Open/Closed Principle (OCP)

Definition: Software entities should be open for extension but closed for modification.

// OncePerRequestFilter is CLOSED for modification
public abstract class OncePerRequestFilter {
public final void doFilter(...) { // final = can't be overridden
// This logic never changes
if (alreadyFiltered) return;
markAsFiltered();
doFilterInternal(...); // But behavior can be EXTENDED
}

protected abstract void doFilterInternal(...); // Extension point
}

// You EXTEND without MODIFYING
public class JwtAuthenticationFilter extends OncePerRequestFilter {
// You didn't change OncePerRequestFilter's code
// You extended its behavior by implementing doFilterInternal
}

Real-world impact:

// Spring can add new filter types without changing the base class:
class CorsFilter extends OncePerRequestFilter { }
class CharacterEncodingFilter extends OncePerRequestFilter { }
class JwtAuthenticationFilter extends OncePerRequestFilter { }
class CustomLoggingFilter extends OncePerRequestFilter { }

// OncePerRequestFilter never changed!

Polymorphism

Definition: Treat objects of different types through a common interface.

// Spring's filter chain doesn't care about specific types:
public class FilterChainProxy {
private List<Filter> filters;

public void doFilter(ServletRequest request, ServletResponse response) {
for (Filter filter : filters) {
// Could be JwtAuthenticationFilter
// Could be CorsFilter
// Could be any Filter implementation
// Spring treats them all the same!
filter.doFilter(request, response, chain);
}
}
}

In your application:

// Spring Security configuration:
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

// Spring doesn't care that jwtAuthenticationFilter is specifically a
// JwtAuthenticationFilter. It only cares that it's a Filter.

// This allows you to:
// 1. Swap filters easily
// 2. Add new filter types
// 3. Reorder filters
// 4. Enable/disable filters dynamically

Code Reuse and Abstraction

Code reuse demonstrated:

// OncePerRequestFilter provides ~100 lines of complex logic
// that EVERY filter extending it gets for free:

public abstract class OncePerRequestFilter extends GenericFilterBean {

private static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";

public final void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) {
// Complex logic you don't have to write:
if (!(request instanceof HttpServletRequest)) {
throw new ServletException("OncePerRequestFilter only supports HTTP");
}

HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;

String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
boolean hasAlreadyFilteredAttribute =
request.getAttribute(alreadyFilteredAttributeName) != null;

if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
filterChain.doFilter(request, response);
return;
}

if (hasAlreadyFilteredAttribute) {
if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
doFilterInternal(httpRequest, httpResponse, filterChain);
return;
}
filterChain.doFilter(request, response);
return;
}

request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(httpRequest, httpResponse, filterChain);
} finally {
request.removeAttribute(alreadyFilteredAttributeName);
}
}

protected abstract void doFilterInternal(...);
}

What you would have written without inheritance:

@Component
public class JwtAuthenticationFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) {
// Copy-paste all 100 lines from above ↑
// Then add your JWT logic
// Repeat for every filter you create
// Bugs multiply across all copies
}
}

Abstraction demonstrated:

// High-level concept: "A filter that runs once per request"
abstract class OncePerRequestFilter

Abstracts away:
- Servlet API complexity
- Dispatch type handling
- Already-filtered tracking
- HTTP request casting

└─ You work at a higher level: "What should happen during filtering?"

6. Common Misconceptions and Insights

Misconception 1: "Inheritance means copying code"

Wrong mental model:

JwtAuthenticationFilter gets a COPY of OncePerRequestFilter's code

Correct mental model:

JwtAuthenticationFilter REFERENCES OncePerRequestFilter's code
└─ Only one copy of doFilter() exists in memory
└─ JwtAuthenticationFilter uses it through the parent reference

Evidence:

JwtAuthenticationFilter filter = new JwtAuthenticationFilter();

// When you call:
filter.doFilter(request, response, chain);

// Java looks for doFilter() in JwtAuthenticationFilter
// Not found! Looks in parent (OncePerRequestFilter)
// Found! Executes the parent's version

// No code duplication in memory!

Misconception 2: "Private members are inherited"

Wrong:

public class Parent {
private String secret = "hidden";
}

public class Child extends Parent {
public void reveal() {
System.out.println(secret); // Compilation error!
}
}

What actually happens:

Memory layout of Child object:
┌──────────────────────┐
│ Parent's private │ ← Exists in memory
│ fields │ ← But not accessible
├──────────────────────┤
│ Parent's protected/ │ ← Accessible
│ public fields │
├──────────────────────┤
│ Child's fields │
└──────────────────────┘

Why this matters in your filter:

public abstract class OncePerRequestFilter {
// You CANNOT access this directly:
private static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";

// But you CAN use the public/protected methods that use it:
protected String getAlreadyFilteredAttributeName() {
return getClass().getName() + ALREADY_FILTERED_SUFFIX;
}
}

public class JwtAuthenticationFilter extends OncePerRequestFilter {
public void someMethod() {
// String suffix = ALREADY_FILTERED_SUFFIX; // ❌ Won't compile
String name = getAlreadyFilteredAttributeName(); // ✓ Works
}
}

Misconception 3: "Multiple inheritance is just extending multiple classes"

Why Java doesn't allow it:

// Imagine if Java allowed this (it doesn't):
class FilterA {
public void doFilter() {
System.out.println("Filter A");
}
}

class FilterB {
public void doFilter() {
System.out.println("Filter B");
}
}

// The Diamond Problem:
class MyFilter extends FilterA, FilterB { // ❌ Not allowed in Java
// Which doFilter() should I use?
// FilterA's or FilterB's?
// Ambiguity!
}

Java's solution: Single inheritance + multiple interfaces

// You can extend ONE class:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
}

// But implement MULTIPLE interfaces:
public class JwtAuthenticationFilter extends OncePerRequestFilter
implements InitializingBean, DisposableBean, Ordered {
// No ambiguity because interfaces only declare methods,
// they don't implement them
}

Misconception 4: "Inheritance vs Composition - always prefer inheritance"

When to use inheritance (IS-A):

// JwtAuthenticationFilter IS-A OncePerRequestFilter ✓
public class JwtAuthenticationFilter extends OncePerRequestFilter {
}

// Makes sense: A JWT filter is a type of request filter

When to use composition (HAS-A):

// JwtAuthenticationFilter HAS-A JwtUtil ✓
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil; // Composition
}

// Makes sense: A JWT filter has a JWT utility,
// but it's not a type of JWT utility

Wrong use of inheritance:

// ❌ BAD: Just to reuse code
public class JwtAuthenticationFilter extends JwtUtil {
// JwtAuthenticationFilter IS-A JwtUtil?
// No! This makes no sense semantically
}

Favor composition over inheritance when:

  • Relationship is "uses" rather than "is a"
  • You need functionality from multiple sources
  • Relationship might change at runtime
  • You want loose coupling

Example from your codebase:

public class JwtAuthenticationFilter extends OncePerRequestFilter {
// Inheritance: IS-A relationship
// JwtAuthenticationFilter IS-A type of OncePerRequestFilter

@Autowired
private JwtUtil jwtUtil; // Composition: HAS-A relationship
// JwtAuthenticationFilter HAS-A JwtUtil

@Autowired
private UserService userService; // Composition: HAS-A relationship
// JwtAuthenticationFilter HAS-A UserService

// Perfect balance: Inherit behavior, compose functionality
}

Insight 1: Protected Members Enable Extension

Protected is the "inheritance keyword":

public abstract class OncePerRequestFilter {

// Protected = designed for child classes to override
protected boolean shouldNotFilter(HttpServletRequest request) {
return false; // Default: don't skip any requests
}

// Protected = designed for child classes to use
protected String getAlreadyFilteredAttributeName() {
return getClass().getName() + ".FILTERED";
}

// Private = internal implementation, don't touch
private boolean skipDispatch(HttpServletRequest request) {
return isAsyncDispatch(request) && shouldNotFilterAsyncDispatch();
}

// Public = external API, everyone can use
public final void doFilter(...) { }
}

In your filter, you could leverage protected methods:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

// Override protected method to customize behavior
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getServletPath();

// Skip JWT validation for public endpoints
return path.equals("/api/auth/login") ||
path.equals("/api/auth/register") ||
path.startsWith("/api/products") || // Public browsing
path.startsWith("/h2-console"); // Dev only
}

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
// Your JWT logic here

// You can use protected methods from parent:
String attributeName = getAlreadyFilteredAttributeName();
logger.debug("Filter attribute: {}", attributeName);

filterChain.doFilter(request, response);
}
}

Insight 2: Constructor Chaining

How constructors work with inheritance:

public abstract class OncePerRequestFilter extends GenericFilterBean {

public OncePerRequestFilter() {
// Parent constructor
logger.info("OncePerRequestFilter created");
}
}

public class JwtAuthenticationFilter extends OncePerRequestFilter {

private JwtUtil jwtUtil;

@Autowired
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
super(); // Calls parent constructor (implicit if not specified)
this.jwtUtil = jwtUtil;
logger.info("JwtAuthenticationFilter created");
}
}

Execution order:

new JwtAuthenticationFilter(jwtUtil)

1. GenericFilterBean constructor runs

2. OncePerRequestFilter constructor runs

3. JwtAuthenticationFilter constructor runs

Object fully initialized

Important: super() must be first statement:

public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil; // ❌ Error if super() is needed
super(); // ❌ Must be first statement
}

public JwtAuthenticationFilter(JwtUtil jwtUtil) {
super(); // ✓ Correct order
this.jwtUtil = jwtUtil;
}

Insight 3: The @Override Annotation is Your Friend

What @Override does:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

@Override // Compiler verifies this actually overrides a parent method
protected void doFilterInternal(...) {
// If you mistype the signature, compiler error!
}

// Without @Override - this would silently fail:
protected void doFilterInternall(...) { // Typo! "Internall"
// Compiler thinks this is a NEW method
// Parent's abstract method is never implemented
// Runtime error when Spring tries to use the filter
}
}

Always use @Override when overriding - it catches:

  • Typos in method names
  • Wrong parameter types
  • Wrong return types
  • Method removed from parent (breaking change)

Insight 4: Final Methods Create Unbreakable Contracts

Why OncePerRequestFilter's doFilter() is final:

public abstract class OncePerRequestFilter {

// final = children CANNOT override this
public final void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) {
// This logic MUST execute exactly as written
// No child class can bypass or modify it

if (alreadyFiltered) return;
markAsFiltered();
doFilterInternal(request, response, (FilterChain) filterChain);
}

// Children must implement this instead
protected abstract void doFilterInternal(...);
}

Without final, you could break the contract:

// If doFilter() wasn't final:
@Component
public class BadJwtFilter extends OncePerRequestFilter {

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) {
// Bypass the once-per-request logic
doFilterInternal((HttpServletRequest) request,
(HttpServletResponse) response, chain);
// Now it could run multiple times!
// The whole point of OncePerRequestFilter is defeated
}
}

Final ensures:

  • Core algorithm can't be bypassed
  • Contract guarantees are maintained
  • Extension points are used correctly

Summary: The Power of Inheritance in Action

What You Gained by Extending OncePerRequestFilter

Code you didn't have to write:

- ~100 lines of filter lifecycle management
- ~50 lines of dispatch type handling
- ~30 lines of HTTP type casting
- ~20 lines of already-filtered tracking
= ~200 lines saved per filter!

Guarantees you got for free:

  • ✓ Filter runs exactly once per request
  • ✓ Handles async dispatches correctly
  • ✓ Handles error dispatches correctly
  • ✓ HTTP types available immediately
  • ✓ Integration with Spring Security
  • ✓ Proper filter chain continuation

What you focused on:

@Override
protected void doFilterInternal(...) {
// ONLY your JWT validation logic
// ~40 lines of focused, business-specific code
}

The Inheritance Hierarchy in Your Application

GenericFilterBean (Spring's base filter)

│ extends

OncePerRequestFilter (Spring's template)

│ extends

JwtAuthenticationFilter (Your implementation)

Each level adds value:

  • GenericFilterBean: Spring bean integration, configuration support
  • OncePerRequestFilter: Once-per-request guarantee, HTTP support
  • JwtAuthenticationFilter: JWT validation, authentication setup

Key Takeaways

  1. Inheritance is about establishing relationships and contracts

    • IS-A relationship: JwtAuthenticationFilter IS-A OncePerRequestFilter
    • Contract: You must implement doFilterInternal()
  2. Template Method Pattern is powerful

    • Parent defines the algorithm structure (template)
    • Children fill in specific steps (implementation)
    • Guarantees consistency while allowing customization
  3. Favor composition over inheritance for flexibility

    • Extend: OncePerRequestFilter (behavioral relationship)
    • Compose: JwtUtil, UserService (functional dependencies)
  4. Protected members enable extension

    • Override protected methods to customize behavior
    • Use protected methods to leverage parent functionality
  5. Final methods enforce contracts

    • Final prevents breaking core guarantees
    • Abstract forces implementation of required behavior
  6. Inheritance enables polymorphism

    • Spring treats all Filters uniformly
    • Easy to add, remove, reorder filters
    • Testable through interfaces

The ultimate benefit: You wrote 40 lines of focused code and got a production-ready, thread-safe, performant, Spring-integrated authentication filter that handles all edge cases correctly.

That's the power of inheritance when used correctly!


Practice Exercises

Exercise 1: Create a Custom Filter

Create a RequestLoggingFilter that extends OncePerRequestFilter:

@Component
public class RequestLoggingFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) {
// Log request details
// Continue the chain
// Log response status after processing
}
}

Questions:

  1. What do you inherit from OncePerRequestFilter?
  2. Why use OncePerRequestFilter instead of implementing Filter?
  3. How would you exclude certain paths from logging?

Exercise 2: Understand the Hierarchy

Draw the complete inheritance hierarchy:

? (top)

?

OncePerRequestFilter

JwtAuthenticationFilter

Questions:

  1. What's at the top of the hierarchy?
  2. What does each level provide?
  3. Which methods are inherited vs implemented?

Exercise 3: Composition vs Inheritance

Analyze this code:

public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
}

Questions:

  1. Why extend OncePerRequestFilter but compose JwtUtil?
  2. What would be wrong with extending JwtUtil?
  3. When should you use inheritance vs composition?

Conclusion

Understanding how inheritance works with extends and how OncePerRequestFilter provides a template for filters is fundamental to Spring Security development. These patterns enable:

  • Code reuse without duplication
  • Consistency across all filters
  • Extensibility for custom behavior
  • Maintainability through clear contracts

By mastering these concepts, you can confidently create custom filters, understand Spring Security's architecture, and apply OOP principles effectively in your applications.


Next Steps:

  • Review the JwtAuthenticationFilter implementation in backend/src/main/java/com/automotive/ecommerce/config/
  • Trace a request through the filter chain using debugger breakpoints
  • Create your own custom filter extending OncePerRequestFilter
  • Study other Spring Security filters to see the pattern in action