← Tutorials ☕ Java 🧩 DSA 🌿 Spring Boot 🚀 DevOps 🗂 MySQL 🏗 System Design ❓ FAQ 🌐 HTML 🔇 JavaScript ⚛ ReactJS 🌻 NodeJS 🐍 Python 🍏 MongoDB 📋 Cheatsheet
Tutorials › FAQ

OAuth 2.0 & OpenID Connect (OIDC)

Security · Interview Essential

Authorization vs Authentication

OAuth 2.0 is a delegated authorization framework — it lets an app access a user's data in another service without sharing passwords. OIDC is a thin authentication (identity) layer built on top of OAuth 2.0.

The 4 Key Roles

  • Resource Owner — the user who owns the data
  • Client — the app requesting access (your Spring Boot app)
  • Authorization Server — validates identity and issues tokens (Google, Okta, Keycloak)
  • Resource Server — the API being accessed (e.g., Google Calendar API)

Authorization Code Flow (8 Steps)

// Most secure OAuth flow — used by server-side apps 1. User clicks "Login with Google" in your app 2. App redirects to Google with: clientId, redirectUri, responseType=code, scope 3. Google authenticates the user (login page) 4. User sees consent screen: "Allow App X to read your contacts?" 5. User approves 6. Google redirects back to app with short-lived Authorization Code 7. App server exchanges Code + clientSecret → Access Token (server-to-server, never exposed to browser) 8. App uses Access Token to call Google APIs on user's behalf

Token Types

TokenPurposeLifetime
Authorization CodeExchanged for Access Token (one-time use)~60 seconds
Access TokenCredential to call Resource Server APIsShort (minutes–hours)
Refresh TokenGet new Access Tokens without re-loginLong (days–months)
ID Token (OIDC)JWT with identity claims (name, email, sub)Typically same as Access Token

OAuth 2.0 vs OIDC

AspectOAuth 2.0OIDC
Answers"Can this app access X?""Who is this user?"
PurposeAuthorization (access control)Authentication (identity)
Token issuedAccess Token onlyAccess Token + ID Token (JWT)
TriggerAny scopescope includes openid
Use case"Let app post to Twitter""Single Sign-On across apps"
💡 Interview tip: OAuth solves authorization ("what can you do"), OIDC solves authentication ("who are you"). They're not the same thing. OIDC = OAuth 2.0 + identity.

Spring Security & Spring Boot

Security · Interview Essential

Spring Security is a powerful, customisable authentication and access control framework for Java applications. It integrates seamlessly with Spring Boot via auto-configuration.

How Spring Security Works (Filter Chain)

Every HTTP request passes through a chain of security filters before reaching your controller. The most important filter is UsernamePasswordAuthenticationFilter.

// Request flow: HTTP Request → DelegatingFilterProxy → SecurityFilterChain (ordered filters) → UsernamePasswordAuthenticationFilter (handles /login) → BasicAuthenticationFilter → ExceptionTranslationFilter → FilterSecurityInterceptor (checks @PreAuthorize, URL rules) → Your Controller

Minimal Spring Boot Security Setup

// pom.xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> // Just adding the dependency auto-protects ALL endpoints. // Default behaviour: basic auth with generated password printed in console.

Custom Security Configuration

@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/api/public/**").permitAll() // no auth needed .requestMatchers("/api/admin/**").hasRole("ADMIN") // ADMIN only .anyRequest().authenticated() // everything else: login required ) .httpBasic(Customizer.withDefaults()) // or .formLogin() for HTML login page .csrf(csrf -> csrf.disable()); // disable for REST APIs return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.builder() .username("aftab") .password(passwordEncoder().encode("password")) .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); // NEVER store plain-text passwords } }

JWT Authentication with Spring Boot

// Flow for stateless REST APIs (most common in interviews): 1. POST /api/auth/login { username, password } → AuthController validates credentials via AuthenticationManager → On success: generate JWT (signed with secret key) → Return: { "token": "eyJhbG..." } 2. Client sends JWT in header on every request: Authorization: Bearer eyJhbG... 3. JwtAuthFilter (OncePerRequestFilter): → Extract token from header → Validate signature + expiry → Load UserDetails and set Authentication in SecurityContext → Request proceeds to controller // JWT Filter skeleton @Component public class JwtAuthFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws Exception { String header = req.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { String token = header.substring(7); if (jwtService.isValid(token)) { String username = jwtService.extractUsername(token); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, authorities); SecurityContextHolder.getContext().setAuthentication(auth); } } chain.doFilter(req, res); } }

Method-Level Security

@EnableMethodSecurity // add to @Configuration class @PreAuthorize("hasRole('ADMIN')") public void deleteUser(long id) { ... } @PreAuthorize("hasAuthority('user:read') or #id == authentication.principal.id") public User getUser(long id) { ... }
💡 Key interview points: Spring Security uses a Filter Chain. JWT = stateless (no server-side session). BCrypt for password hashing. @PreAuthorize for method-level access control. CSRF disabled for REST APIs (re-enabled for form-based web apps).

equals() & hashCode() in Java

Java Core · Interview Essential

Every Java class inherits equals() and hashCode() from Object. The defaults use reference equality (this == obj). When you store objects in HashMap or HashSet, you must override both correctly.

The Contract (Never Break This)

// THE RULE: if a.equals(b) == true, then a.hashCode() == b.hashCode() MUST be true. // The reverse is NOT required — same hash does NOT mean equal objects (hash collision is OK).

equals() — 5 Rules

  • Reflexive: x.equals(x) → always true
  • Symmetric: if x.equals(y) → then y.equals(x)
  • Transitive: if x.equals(y) and y.equals(z) → then x.equals(z)
  • Consistent: repeated calls return same result if fields unchanged
  • Null-safe: x.equals(null) → always false, never throws NPE

Correct Implementation

public class Employee { private int id; private String name; @Override public boolean equals(Object o) { if (this == o) return true; // same reference if (!(o instanceof Employee)) return false; // null or wrong type Employee e = (Employee) o; return this.id == e.id && Objects.equals(this.name, e.name); } @Override public int hashCode() { return Objects.hash(id, name); // use SAME fields as equals() } }

What Breaks Without Proper Override

// Only equals() overridden, hashCode() NOT overridden → BROKEN Employee e1 = new Employee(1, "Aftab"); Employee e2 = new Employee(1, "Aftab"); e1.equals(e2); // true (our equals says yes) e1.hashCode() == e2.hashCode() // false (different buckets!) → CONTRACT BROKEN Set<Employee> set = new HashSet<>(); set.add(e1); set.contains(e2); // FALSE — e2 lands in different bucket, never found! Map<Employee, String> map = new HashMap<>(); map.put(e1, "Developer"); map.get(e2); // null — key not found despite "equal" object!
⚠️ Always use the same set of fields in both methods. In Java 14+, use record — it auto-generates correct equals() and hashCode().

How HashMap Works Internally in Java

Java Core · Most Asked Interview Question

A HashMap<K,V> is backed by an array of buckets. Each bucket holds a linked list (Java 8+: a balanced tree when the list gets long).

Step-by-Step: How put(key, value) Works

map.put("name", "Aftab"); Step 1: Compute hash of key hash = "name".hashCode() // e.g. 3373752 Step 2: Find bucket index index = hash & (capacity - 1) // fast modulo, default capacity = 16 // e.g. index = 8 Step 3: If bucket[8] is empty → store node there directly Step 4: If bucket[8] has node(s) (collision): → Walk the chain, compare using equals() → If key found: UPDATE the value → If not found: APPEND new node at end of chain

Internal Node Structure

// Simplified — what Java stores in each bucket slot static class Node<K,V> { final int hash; final K key; V value; Node<K,V> next; // pointer to next node in this bucket (for chaining) }

Load Factor & Rehashing

  • Default capacity: 16 buckets (always a power of 2)
  • Default load factor: 0.75
  • Threshold = 16 × 0.75 = 12 — when 12 entries are added, the map resizes
  • Resize: doubles capacity to 32, rehashes all existing entries into new positions

Java 8+ Treeification

// If a single bucket chain grows beyond 8 nodes: // LinkedList → Red-Black Tree (for that bucket only) // Lookup improves from O(n) → O(log n) for that bucket // This handles pathological hashing (e.g., all keys hash to same bucket)

Key Characteristics

PropertyHashMapLinkedHashMapTreeMap
OrderNoneInsertion orderSorted (natural)
get/putO(1) avgO(1) avgO(log n)
Null key1 allowed1 allowedNot allowed
Thread-safe?NoNoNo
Thread-safe altConcurrentHashMapConcurrentSkipListMap
💡 Interview answer checklist: Array of buckets → hash → index → linked list (tree if >8) → equals() for collision → load factor 0.75 → rehash when threshold exceeded.

Comparable vs Comparator in Java

Java Core · Interview Essential

Key Difference at a Glance

ComparableComparator
Packagejava.langjava.util
MethodcompareTo(T obj)compare(T o1, T o2)
Sort strategiesOne — natural orderingMany — multiple orderings
Modifies the class?Yes — class implements itNo — external class
Used byCollections.sort(list)Collections.sort(list, comparator)
Use whenOne obvious default orderingMultiple orderings or can't modify class

Comparable — Natural Ordering

public class Employee implements Comparable<Employee> { int id; String name; double salary; @Override public int compareTo(Employee other) { return Integer.compare(this.id, other.id); // sort by ID — natural order } } List<Employee> list = ...; Collections.sort(list); // uses compareTo() automatically → sorted by id

Comparator — Custom Ordering (Multiple Ways)

// 1. Separate class class BySalary implements Comparator<Employee> { public int compare(Employee a, Employee b) { return Double.compare(a.salary, b.salary); } } Collections.sort(list, new BySalary()); // 2. Lambda (most common in modern Java) list.sort((a, b) -> Double.compare(a.salary, b.salary)); // 3. Method reference — cleanest list.sort(Comparator.comparingDouble(e -> e.salary)); // 4. Chained comparators (sort by name, then by salary if names are equal) list.sort(Comparator.comparing(Employee::getName) .thenComparingDouble(Employee::getSalary)); // 5. Reverse order list.sort(Comparator.comparingInt(Employee::getId).reversed());

Return Value Convention (Both Methods)

  • Negative — first object comes before second
  • Zero — objects are considered equal in ordering
  • Positive — first object comes after second
⚠️ Never use subtraction (a.id - b.id) for numeric comparison — it can overflow. Always use Integer.compare(a.id, b.id) or Double.compare(a.salary, b.salary).

Sort a List of Employee Objects in Java

Java Core · Coding Question

This is one of the most common Java coding interview questions. There are multiple ways — know all of them.

Employee Class

public class Employee { private int id; private String name; private double salary; private int age; // constructor, getters, setters, toString } List<Employee> employees = Arrays.asList( new Employee(3, "Aftab", 75000, 28), new Employee(1, "Zara", 90000, 25), new Employee(2, "Ahmed", 60000, 32) );

1. Sort by Single Field

// By name (alphabetical) employees.sort(Comparator.comparing(Employee::getName)); // By salary (ascending) employees.sort(Comparator.comparingDouble(Employee::getSalary)); // By salary (descending) employees.sort(Comparator.comparingDouble(Employee::getSalary).reversed()); // By id employees.sort(Comparator.comparingInt(Employee::getId));

2. Sort by Multiple Fields

// Sort by department, then by salary descending within each department employees.sort( Comparator.comparing(Employee::getDepartment) .thenComparingDouble(Comparator.comparingDouble(Employee::getSalary).reversed()::compare) ); // Cleaner: sort by name, then age if names are same employees.sort( Comparator.comparing(Employee::getName) .thenComparingInt(Employee::getAge) );

3. Using Stream (returns new sorted list)

// Sort and collect into new list List<Employee> sortedByName = employees.stream() .sorted(Comparator.comparing(Employee::getName)) .collect(Collectors.toList()); // Sort by salary descending, get top 3 List<Employee> top3 = employees.stream() .sorted(Comparator.comparingDouble(Employee::getSalary).reversed()) .limit(3) .collect(Collectors.toList());

4. Using Comparable (natural order in the class)

public class Employee implements Comparable<Employee> { @Override public int compareTo(Employee o) { return Integer.compare(this.id, o.id); // default: sort by id } } Collections.sort(employees); // uses compareTo

5. Sort with null-safety

// nullsFirst / nullsLast handle null values without NullPointerException employees.sort(Comparator.comparing( Employee::getName, Comparator.nullsLast(Comparator.naturalOrder()) ));
💡 Interview summary: Use list.sort(Comparator.comparing(...)) for single field. Chain with .thenComparing() for multiple fields. Use .reversed() for descending. Stream .sorted() returns a new list — original unchanged.

SOLID Principles in Java

Design · Interview Essential

SOLID is a set of 5 object-oriented design principles that make code more maintainable, scalable, and testable.

S — Single Responsibility Principle

A class should have one, and only one, reason to change.

// ❌ Bad: one class doing too many things class Employee { void calculateSalary() { ... } void saveToDatabase() { ... } // ← persistence: different responsibility void generatePayslipPDF() { ... } // ← reporting: different responsibility } // ✅ Good: each class has one job class Employee { void calculateSalary() {} } class EmployeeRepository { void save(Employee e) {} } class PayslipService { void generate(Employee e) {} }

O — Open/Closed Principle

Open for extension, closed for modification. Add new behaviour by adding new code, not by editing existing code.

// ❌ Bad: adding a new vehicle type requires modifying this method double calculateTax(Vehicle v) { if (v instanceof Car) return v.getValue() * 0.10; if (v instanceof Truck) return v.getValue() * 0.20; return 0; } // ✅ Good: each subclass handles its own calculation abstract class Vehicle { abstract double calculateTax(); } class Car extends Vehicle { double calculateTax() { return getValue() * 0.10; } } class Truck extends Vehicle { double calculateTax() { return getValue() * 0.20; } } // Add Bike? Just add new class — don't touch existing code

L — Liskov Substitution Principle

Subclasses must be substitutable for their superclass without breaking the program.

// ❌ Bad: Square extends Rectangle but breaks its contract class Rectangle { setWidth(w); setHeight(h); getArea() = w*h; } class Square extends Rectangle { setWidth(w) { this.width = this.height = w; } // also sets height! breaks Rectangle's contract } // Using Square as Rectangle → unexpected area calculations // ✅ Good: both extend Shape independently abstract class Shape { abstract double getArea(); } class Rectangle extends Shape { double getArea() { return width * height; } } class Square extends Shape { double getArea() { return side * side; } }

I — Interface Segregation Principle

No class should be forced to implement methods it doesn't use. Prefer small, focused interfaces.

// ❌ Bad: one fat interface forces Bike to implement openDoors() interface Vehicle { startEngine(); openDoors(); fly(); } class Bike implements Vehicle { void openDoors() { throw new UnsupportedOperationException(); } } // ✅ Good: split into focused interfaces interface Motorized { void startEngine(); } interface HasDoors { void openDoors(); } class Car implements Motorized, HasDoors { ... } class Bike implements Motorized { ... } // no forced openDoors()

D — Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions.

// ❌ Bad: Car is tightly coupled to PetrolEngine class Car { private PetrolEngine engine = new PetrolEngine(); // hard dependency } // ✅ Good: Car depends on abstraction (interface), not concrete class interface Engine { void start(); } class PetrolEngine implements Engine { public void start() { ... } } class ElectricEngine implements Engine { public void start() { ... } } class Car { private final Engine engine; public Car(Engine engine) { this.engine = engine; } // injected — swap engine freely } // Spring's @Autowired implements DIP — inject any implementation of an interface

Java Design Patterns

Design · Interview Essential

Design patterns are reusable solutions to common software design problems. Categorised into three groups: Creational, Structural, and Behavioural.

Creational Patterns

Singleton — One instance, globally accessible

public class DatabaseConnection { private static volatile DatabaseConnection instance; private DatabaseConnection() {} // private constructor public static DatabaseConnection getInstance() { if (instance == null) { synchronized (DatabaseConnection.class) { if (instance == null) // double-checked locking (thread-safe) instance = new DatabaseConnection(); } } return instance; } } // Use: DatabaseConnection.getInstance() // Used by: Spring Beans (default scope), Logger, Config managers

Factory Method — Let subclasses decide what to create

interface Notification { void send(String msg); } class EmailNotification implements Notification { public void send(String m) { ... } } class SMSNotification implements Notification { public void send(String m) { ... } } class NotificationFactory { public static Notification create(String type) { return switch (type) { case "EMAIL" -> new EmailNotification(); case "SMS" -> new SMSNotification(); default -> throw new IllegalArgumentException("Unknown type"); }; } } Notification n = NotificationFactory.create("EMAIL"); n.send("Hello!");

Builder — Construct complex objects step by step

public class User { private final String name; private final String email; private final int age; private User(Builder b) { this.name=b.name; this.email=b.email; this.age=b.age; } public static class Builder { private String name; private String email; private int age; public Builder name(String v) { this.name=v; return this; } public Builder email(String v) { this.email=v; return this; } public Builder age(int v) { this.age=v; return this; } public User build() { return new User(this); } } } User u = new User.Builder().name("Aftab").email("a@b.com").age(28).build(); // Used by: StringBuilder, Lombok @Builder, Spring's UriComponentsBuilder

Structural Patterns

Decorator — Add behaviour without modifying the class

interface Coffee { double cost(); } class SimpleCoffee implements Coffee { public double cost() { return 50; } } class MilkDecorator implements Coffee { private Coffee coffee; MilkDecorator(Coffee c) { this.coffee = c; } public double cost() { return coffee.cost() + 10; } // wraps and adds } Coffee c = new MilkDecorator(new SimpleCoffee()); // cost = 60 // Used by: Java I/O (BufferedReader wraps FileReader), Spring Security filter chain

Proxy — Control access to an object

interface Service { String getData(); } class RealService implements Service { public String getData() { return "data"; } } class CachingProxy implements Service { private RealService real = new RealService(); private String cache = null; public String getData() { if (cache == null) cache = real.getData(); // call real only once return cache; } } // Used by: Spring AOP (@Transactional, @Cacheable create proxies), Hibernate lazy loading

Behavioural Patterns

Observer — Notify multiple objects when state changes

interface Observer { void update(String event); } class EventBus { private List<Observer> observers = new ArrayList<>(); void subscribe(Observer o) { observers.add(o); } void publish(String event) { observers.forEach(o -> o.update(event)); } } // Used by: Spring's ApplicationEvent, Java's java.util.Observer, RxJava

Strategy — Swap algorithms at runtime

interface SortStrategy { void sort(int[] arr); } class BubbleSort implements SortStrategy { public void sort(int[] a) { ... } } class QuickSort implements SortStrategy { public void sort(int[] a) { ... } } class Sorter { private SortStrategy strategy; void setStrategy(SortStrategy s) { this.strategy = s; } void sort(int[] arr) { strategy.sort(arr); } } // Used by: Comparator (it IS a strategy), Spring Security authentication strategies
💡 Spring Boot uses patterns everywhere: Singleton (@Bean default scope) · Factory (BeanFactory) · Proxy (@Transactional, @Cacheable) · Observer (ApplicationEvent) · Template Method (JdbcTemplate) · Decorator (Filter chain)

Microservices Design Patterns

Architecture · Interview Essential

Microservices break a monolith into independently deployable services. These patterns solve the common problems that arise.

Decomposition Patterns

1. Decomposition by Business Capability

Split the monolith along business capabilities: Order Service, Payment Service, Inventory Service, User Service — each maps to one team's ownership.

2. Strangler Fig Pattern

Gradually replace a monolith — new microservices intercept requests for specific features, wrapping the legacy system. The monolith "dies" as each feature is migrated.

Communication Patterns

3. API Gateway

Single entry point for all clients. Handles routing, authentication, rate limiting, SSL termination, and response aggregation. Prevents clients from calling multiple services directly.

Client → API Gateway (Spring Cloud Gateway / Netflix Zuul) ↓ ↓ ↓ Order Service User Service Payment Service

4. Aggregator Pattern

A composite service calls multiple downstream services, combines their responses, and returns one consolidated response to the client.

5. Circuit Breaker (Resilience4j)

// States: CLOSED → requests pass through normally ↓ (failures exceed threshold, e.g. 50% in 10 seconds) OPEN → all requests fail-fast (no call to downstream service) ↓ (after timeout, e.g. 30 seconds) HALF-OPEN → test requests allowed through ↓ (if test succeeds) → back to CLOSED ↓ (if test fails) → back to OPEN // Prevents cascading failure: if Payment Service is down, // Order Service doesn't hang waiting — it fails fast immediately @CircuitBreaker(name = "paymentService", fallbackMethod = "fallback") public Order processPayment(Order order) { ... } public Order fallback(Order order, Exception e) { return "Payment queued for retry"; }

Data Patterns

6. Database Per Service

Each microservice has its own database. No shared DB between services. Services communicate via APIs or events — never direct DB joins across services.

7. CQRS (Command Query Responsibility Segregation)

Separate the write model (Commands: create/update/delete) from the read model (Queries: optimised views). Read replicas can be highly denormalized for performance.

// Command side: handles writes, emits events POST /orders → OrderCommandService → saves to write DB → publishes OrderCreatedEvent // Query side: subscribes to events, builds read-optimised views OrderCreatedEvent → OrderProjectionService → updates read DB (denormalised view) GET /orders/{id} → queries read DB directly → fast response

8. Event Sourcing

Instead of storing current state, store all events that led to that state. The current state is derived by replaying events. Enables full audit trail and time-travel debugging.

Async Communication

9. Asynchronous Messaging (Kafka / RabbitMQ)

Services communicate via a message broker instead of synchronous HTTP. The publisher doesn't wait for the consumer. Decouples services and enables retry/dead-letter queuing.

💡 Interview tip — know these for sure: API Gateway, Circuit Breaker, Database-per-Service, CQRS, Saga pattern (for distributed transactions across services).

12-Factor App with Spring Boot

Architecture · Modern Best Practice

The 12-Factor methodology is a set of best practices for building scalable, maintainable, cloud-native applications. Spring Boot is built to align with these principles.

#FactorPrincipleSpring Boot Implementation
1CodebaseOne repo, many deploysGit, one repo per service
2DependenciesDeclare all deps explicitlypom.xml with spring-boot-starter-*
3ConfigConfig in environment, not code${DB_URL} in application.properties; Spring Cloud Config
4Backing ServicesDB/queue = attached resourceSwap DB via config — no code change (@Repository)
5Build/Release/RunStrict stage separationmvn package → release JAR → java -jar
6ProcessesStateless, share-nothingREST endpoints hold no session state; sessions in Redis
7Port BindingSelf-contained, export via portEmbedded Tomcat: java -jar app.jar → runs on port 8080
8ConcurrencyScale out via process modelRun multiple instances behind Kubernetes / load balancer
9DisposabilityFast startup, graceful shutdownIdempotent endpoints; server.shutdown=graceful
10Dev/Prod ParityKeep environments similarDocker containers ensure identical runtime
11LogsTreat logs as event streamsSLF4J → stdout → ELK (Elasticsearch, Logstash, Kibana)
12Admin ProcessesOne-off tasks in same environmentFlyway/Liquibase DB migrations; Spring Batch jobs

Factor 3 (Config) — Most Important in Practice

# application.properties — never hardcode env-specific values spring.datasource.url = jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME} spring.datasource.username = ${DB_USER} spring.datasource.password = ${DB_PASS} jwt.secret = ${JWT_SECRET} # Set via environment variables, Docker secrets, or Kubernetes ConfigMaps # This same code runs in dev, staging, and production unchanged
💡 Why it matters: Following 12-Factor makes your app cloud-native — easy to containerise, scale horizontally, deploy to Kubernetes, and operate in CI/CD pipelines. It's the foundation of modern DevOps.