Java & Backend Engineering
Complete Study Guide
■ Core Java ■ Spring
OOP · SOLID · JVM · Java 21 · Design Patterns IoC · Boot · AOP · JPA · Security · Batch
■ Microservices ■■ Angular
DDD · Saga · CQRS · Kafka · gRPC · Resilience NgRx · Signals · Forms · Performance
■■ Data ■■ Cloud & DevOps
SQL · MongoDB · Cassandra · Redis · Elasticsearch Docker · K8s · AWS · Terraform · CI/CD
■ Security ■ Observability
OAuth2 · JWT · OWASP · mTLS · RBAC/ABAC Tracing · Metrics · Logs · SLO · Chaos
■■ Architecture ■ Testing
CAP · 12-Factor · Hexagonal · System Design Pyramid · TestContainers · Pact · Performance
Senior-Level Depth · Full Explanations · Code Examples · Interview Ready
Java & Backend Engineering — Complete Study Guide Page 1
■ Section 1 — Core Java
1.1 OOP Principles & SOLID CORE
Object-Oriented Principles
■ Encapsulation — Bundling data and methods into a class while hiding internal state. Fields
are private; exposed via controlled accessors. Protects invariants and lets internal implementation
change without breaking external code.
■ Inheritance — A subclass extends a superclass, reusing and overriding behaviour. Java
allows single class inheritance but multiple interface implementation. Favour composition over
deep inheritance hierarchies to reduce coupling.
■ Polymorphism — Same interface, different implementations. Compile-time: method
overloading (same name, different signatures). Runtime: method overriding via dynamic dispatch
— the JVM calls the overriding method based on the actual object type, not the reference type.
■ Abstraction — Expose only what is necessary; hide complexity. Abstract classes provide
partial implementation; interfaces define pure contracts. Use to reduce cognitive load and
decouple callers from implementation details.
SOLID Principles
■ S — Single Responsibility — A class should have one reason to change. An OrderService
should not contain both order logic and email-sending. Split into OrderService +
NotificationService. Makes each class smaller, focused, and independently testable.
■ O — Open/Closed — Open for extension, closed for modification. Add behaviour via new
classes (inheritance, composition, strategy pattern) — not by editing existing stable code.
Protects against regression.
■ L — Liskov Substitution — Subtypes must be fully substitutable for their base type. If Bird
has fly(), then Penguin extends Bird violates LSP. Solution: restructure so only FlyingBird has
fly(). Violations often manifest as type-checking (instanceof) in calling code.
■ I — Interface Segregation — Many specific interfaces beat one fat interface. A simple Printer
should not be forced to implement scan() and fax(). Split into Printable, Scannable, Faxable.
Clients only depend on what they actually use.
■ D — Dependency Inversion — High-level modules should not depend on low-level modules.
Both should depend on abstractions (interfaces). OrderService depends on PaymentGateway
(interface), not StripePaymentGateway (concrete class). Swap implementations without touching
OrderService.
Java & Backend Engineering — Complete Study Guide Page 2
// Dependency Inversion — OrderService depends on interface, not concrete class
public interface PaymentGateway {
PaymentResult process(Payment payment);
}
// Concrete implementations — injected at runtime by Spring
public class StripeGateway implements PaymentGateway { ... }
public class PayPalGateway implements PaymentGateway { ... }
// High-level service — no import of Stripe or PayPal
@Service
public class OrderService {
private final PaymentGateway gateway; // abstraction
public OrderService(PaymentGateway gateway) { [Link] = gateway; }
public void placeOrder(Order order) {
[Link]([Link]()); // works for ANY gateway
}
}
1.2 Generics, Collections & Streams CORE
Generics
Generics provide compile-time type safety and eliminate explicit casting. The JVM uses type erasure
— generic type info is removed at bytecode level, replaced with Object or the declared upper bound.
Consequence: you cannot do new T[] or check instanceof List at runtime.
Bounded wildcards — PECS rule (Producer Extends, Consumer Super): (covariant) — you can
only READ from the collection. (contravariant) — you can only WRITE to the collection. When both
reading and writing, use the concrete type .
// Bounded generic method
public static <T extends Comparable<T>> T max(List<T> list) {
return [Link]().max([Link]()).orElseThrow();
}
// PECS: src produces (extends), dest consumes (super)
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T elem : src) [Link](elem);
}
Collections — Key Data Structures
■ ArrayList — Dynamic array. O(1) random access, O(1) amortised add at tail, O(n)
insert/delete at middle. Grows by 50% on resize. Best for: random-access reads, iteration.
Java & Backend Engineering — Complete Study Guide Page 3
■ LinkedList — Doubly linked list. O(1) add/remove at head/tail. O(n) random access. Higher
memory (two pointers per node). Use for frequent add/remove at ends.
■ HashMap — Hash table. O(1) average get/put. Load factor 0.75 — resizes when 75% full.
Java 8+: bucket chains become Red-Black TreeNodes when length > 8 (prevents O(n) worst
case).
■ TreeMap — Red-Black tree. O(log n) all ops. Keys always sorted. Use for sorted iteration,
headMap/tailMap/subMap range queries.
■ LinkedHashMap — HashMap + insertion-order doubly-linked list. Override
removeEldestEntry() for LRU cache.
■ ConcurrentHashMap — Thread-safe HashMap. Java 8+: CAS + per-bin synchronisation (no
global lock). High-concurrency O(1) reads/writes. Prefer over [Link] in all
high-throughput scenarios.
■ PriorityQueue — Binary min-heap. O(log n) add/poll, O(1) peek. Max-heap: new
PriorityQueue<>([Link]()).
■ ArrayDeque — Resizable circular array. Faster than LinkedList as a stack or queue. Preferred
over [Link] (synchronized legacy class).
Streams API
Streams are lazy evaluation pipelines. Intermediate operations (filter, map, flatMap, sorted, distinct,
limit, peek) are lazy — nothing executes until a terminal operation (collect, forEach, reduce, count,
findFirst, anyMatch, toList) is called. This enables short-circuit optimisation and fusion of operations.
// Top 3 salaries of active Engineering employees
List<Double> top3 = [Link]()
.filter(e -> [Link]() && "Engineering".equals([Link]()))
.sorted([Link](Employee::getSalary).reversed())
.limit(3)
.map(Employee::getSalary)
.collect([Link]());
// Grouping — count per department
Map<String, Long> byDept = [Link]()
.collect([Link](Employee::getDept, [Link]()));
// FlatMap — flatten List<List<Skill>> to List<Skill>
List<String> allSkills = [Link]()
.flatMap(e -> [Link]().stream())
.distinct().sorted().collect([Link]());
Parallel streams use the common ForkJoinPool (CPU cores − 1 threads). Only beneficial for CPU-bound,
stateless, order-independent work on large datasets (typically 10 000+ elements). For I/O-bound work, small
collections, or tasks with shared mutable state, parallel streams add overhead with no benefit.
Java & Backend Engineering — Complete Study Guide Page 4
1.3 Concurrency & Multithreading ADVANCED
Core Threading Problems
■ Race Condition — Two threads read-modify-write shared state without synchronisation,
producing unpredictable results. Fix: synchronize the compound action.
■ Deadlock — Thread A holds Lock 1 and waits for Lock 2; Thread B holds Lock 2 and waits for
Lock 1 — both wait forever. Prevention: always acquire locks in a globally consistent order.
■ Livelock — Threads keep responding to each other but make no progress (both keep retrying
and backing off indefinitely).
■ Starvation — A thread can never acquire CPU time because other threads constantly take
priority.
■ volatile keyword — Guarantees visibility (reads/writes bypass CPU cache, go to main
memory) but NOT atomicity. i++ on a volatile int is still a race condition — it is read-modify-write.
Use AtomicInteger for atomic compound operations.
ExecutorService & Thread Pools
Never create raw threads in production code. Use ExecutorService to manage lifecycle, pooling, and
error handling. [Link](n) — n threads, unbounded queue (risk: OOM);
newCachedThreadPool() — creates threads on demand, risky under sustained high load;
newVirtualThreadPerTaskExecutor() — Java 21 virtual threads, ideal for I/O-bound.
// Fine-grained control — prefer this in production
ExecutorService pool = new ThreadPoolExecutor(
4, // corePoolSize
10, // maximumPoolSize
60L, [Link], // keepAlive for idle threads above core
new LinkedBlockingQueue<>(500), // bounded queue — prevents OOM
new CallerRunsPolicy() // back-pressure: caller thread runs on rejection
);
CompletableFuture — Async Composition
CompletableFuture (Java 8+) enables non-blocking, composable async programming. thenApply —
synchronous transform of result. thenApplyAsync — runs transform on executor thread.
thenCompose — chains futures (flatMap). thenCombine — merges two independent futures when
both complete. allOf — wait for all; anyOf — wait for first.
Java & Backend Engineering — Complete Study Guide Page 5
CompletableFuture<DashboardDTO> dashboard =
[Link](() -> fetchUser(userId), pool)
.thenCombine(
[Link](() -> fetchOrders(userId), pool),
(user, orders) -> new DashboardDTO(user, orders)
)
.orTimeout(3, [Link])
.exceptionally(ex -> [Link]());
Key Synchronizers in [Link]
■ ReentrantLock — Explicit lock with tryLock(timeout), lockInterruptibly(), and fair mode.
Always release in finally block.
■ ReadWriteLock — Multiple readers OR one writer. For read-heavy shared data structures
(e.g., in-memory caches).
■ Semaphore — Limits concurrent access to a pool of N resources (e.g., max 10 concurrent DB
connections).
■ CountDownLatch — One-time gate. Main thread calls await(); N worker threads each call
countDown(). When count reaches 0, main proceeds. Non-reusable.
■ CyclicBarrier — Reusable. All N threads wait at the barrier; when all arrive, all proceed
simultaneously. Good for phased parallel computations.
■ StampedLock — Optimistic read locking for low-contention read-heavy scenarios. Faster than
ReadWriteLock when reads rarely conflict with writes.
WARNING: Always release locks in finally blocks. If acquiring multiple locks, always acquire in a globally
consistent order to prevent deadlocks. Prefer higher-level abstractions (CompletableFuture, BlockingQueue,
concurrent collections) over raw synchronized blocks.
1.4 JVM Internals — GC, Memory Model, Class Loading ADVANCED
JVM Memory Areas
■ Heap — All objects and arrays. Shared across threads. Young Generation (Eden + Survivor
S0/S1) + Old Generation (Tenured). Minor GC collects Young Gen frequently and cheaply;
Major/Full GC collects Old Gen.
■ Metaspace — Class metadata, method bytecode. Replaced PermGen in Java 8+. Grows
dynamically up to native memory limit. OOM if excessive dynamic class generation (e.g., proxy
frameworks, broken hot-reload).
■ Stack (per thread) — Each method call creates a stack frame: local variables, operand stack,
method reference. StackOverflowError on unbounded recursion.
Java & Backend Engineering — Complete Study Guide Page 6
■ Code Cache — JIT-compiled native code. If full, JVM falls back to interpreted mode —
catastrophic performance drop. Monitor with -XX:+PrintCodeCache.
Garbage Collection Algorithms
■ Serial GC — Single-threaded, stop-the-world. Small applications or containers with a single
CPU.
■ Parallel GC — Multi-threaded minor and major GC. Maximises throughput. Was default before
Java 9.
■ G1 GC (default Java 9+) — Heap divided into equal-sized regions (1–32 MB each).
Incremental mixed collections. Configurable pause targets via -XX:MaxGCPauseMillis. Best
general-purpose choice.
■ ZGC (Java 15+ production) — Sub-millisecond pauses. Concurrent marking and relocation
using load barriers. Scales to multi-TB heaps. Use when pause latency is critical.
■ Shenandoah — Similar goals to ZGC. Concurrent compaction. Red Hat contribution, available
since Java 12.
Production tuning: set -Xms equal to -Xmx to prevent heap resizing. Use G1GC with
-XX:MaxGCPauseMillis=200. Analyse GC logs using [Link]. Long GC pauses in microservices cause
cascading timeouts — monitor p99 GC pause time as a key SLI.
Java Memory Model (JMM) & Class Loading
The JMM defines visibility guarantees: a write to a volatile variable happens-before every
subsequent read of it. A synchronized block release happens-before the next acquire on the same
monitor. Without these guarantees, CPU and compiler reordering can cause subtle,
hard-to-reproduce bugs.
Class loading delegation hierarchy: Bootstrap ClassLoader (JDK core classes) → Platform
ClassLoader → Application ClassLoader (your classpath). Custom ClassLoaders enable hot-reload
(Spring DevTools) and modular systems (OSGi). Phases: Loading → Linking (Verify + Prepare +
Resolve) → Initialization (run static initializers).
1.5 Java 11–21 Modern Features ADVANCED
Records (Java 16)
Immutable data carriers. Compiler auto-generates canonical constructor, accessors (field name, no
get prefix), equals(), hashCode(), and toString(). Records implicitly extend [Link]
— cannot extend other classes. Perfect for DTOs, value objects, and event payloads.
Java & Backend Engineering — Complete Study Guide Page 7
public record PaymentRequest(String accountId, BigDecimal amount, String currency) {
// Compact constructor — validation only
public PaymentRequest {
[Link](accountId);
if ([Link]() <= 0) throw new IllegalArgumentException("Must be positive");
}
}
// Accessor: [Link]() — NOT [Link]()
var req = new PaymentRequest("ACC001", new BigDecimal("100.00"), "INR");
Sealed Classes (Java 17)
Restricts which classes/interfaces may extend or implement a type. Permitted subclasses must be
final, sealed, or non-sealed. Enables exhaustive pattern matching — the compiler verifies every
case is handled, turning a runtime error into a compile-time error.
public sealed interface PaymentResult
permits PaymentSuccess, PaymentFailure, PaymentPending {}
public record PaymentSuccess(String txnId, Instant at) implements PaymentResult {}
public record PaymentFailure(String reason) implements PaymentResult {}
public record PaymentPending(String referenceId) implements PaymentResult {}
// Exhaustive switch — compile error if a case is missing
String msg = switch (result) {
case PaymentSuccess s -> "OK: " + [Link]();
case PaymentFailure f -> "FAILED: " + [Link]();
case PaymentPending p -> "PENDING: " + [Link]();
};
Virtual Threads — Project Loom (Java 21)
Virtual threads are JVM-managed lightweight threads. A platform (OS) thread consumes ~1 MB
stack; a virtual thread consumes a few KB. When a virtual thread blocks on I/O, the JVM unmounts it
from the carrier platform thread (which picks up another virtual thread) and remounts it when I/O
completes. Result: write simple blocking code that scales to millions of concurrent requests.
Java & Backend Engineering — Complete Study Guide Page 8
// One virtual thread per request — handles 100 000 concurrent tasks easily
try (var executor = [Link]()) {
[Link](0, 100_000).forEach(i ->
[Link](() -> {
var data = [Link]("/api/item/" + i); // blocks — yields carrier
[Link](data); // blocks — yields carrier
})
);
}
// Spring Boot 3.2+: enable with ONE property
// [Link]=true
Virtual threads excel at I/O-bound workloads (DB, HTTP, file). They do NOT improve CPU-bound work —
use ForkJoinPool for that. Avoid thread-local variables in virtual threads; they prevent carrier thread GC.
Other Key Features (Java 11–21)
■ Text Blocks (Java 15) — Multi-line strings with """...""". Strips common leading whitespace.
Ideal for embedded SQL, JSON, HTML.
■ Switch Expressions (Java 14) — Switch returns a value; arrow syntax eliminates
fall-through. Used in pattern matching switches above.
■ Pattern Matching instanceof (Java 16) — if (obj instanceof String s) { use s
directly; } — no explicit cast needed. Variable scoped to true-branch.
■ var — Local Type Inference (Java 10) — Compiler infers type from right-hand side. Only for
local variables. No runtime effect.
■ Structured Concurrency (Java 21 Preview) — Treat a group of virtual threads as a single
unit of work. If any subtask fails, cancel the rest automatically — clean failure handling without
complex CompletableFuture chains.
1.6 Design Patterns — Gang of Four (GoF) PATTERN
Creational Patterns
■ Singleton — One instance per JVM. Use enum-based singleton — handles serialisation and
reflection attacks automatically. Spring beans are singletons by default.
■ Factory Method — Defines an interface for creating an object; subclasses decide which
concrete class to instantiate. Decouples creation from usage.
■ Abstract Factory — Creates families of related objects without specifying concrete classes.
E.g., UIComponentFactory returning Windows or Mac variants of Button and Dialog.
■ Builder — Constructs complex objects step by step. Avoids telescoping constructors. Lombok
@Builder generates this automatically. Allows optional fields with a clear API.
Java & Backend Engineering — Complete Study Guide Page 9
■ Prototype — Creates new objects by cloning an existing one. Useful when object creation is
expensive and a similar instance already exists.
Structural Patterns
■ Adapter — Wraps an incompatible interface behind the expected one. E.g., wrapping a legacy
SOAP payment service behind a modern PaymentGateway interface.
■ Decorator — Adds behaviour dynamically without subclassing. Java I/O streams:
BufferedReader wraps InputStreamReader wraps FileReader — each layer adds behaviour
(buffering, charset decoding).
■ Proxy — Adds indirection — controls access, adds behaviour, or defers creation. Spring AOP
uses JDK Dynamic Proxies or CGLIB proxies to intercept @Transactional, @Cacheable, @Async
method calls.
■ Facade — Simplified interface over a complex subsystem. [Link]() hides
the coordination of InventoryService, PaymentService, and NotificationService from callers.
■ Composite — Treats individual objects and compositions uniformly. File system tree: File and
Directory both implement FileSystemNode with getSize(). Works recursively.
Behavioural Patterns
Strategy — Encapsulate interchangeable algorithms. Client selects strategy at runtime. Eliminates
if/else chains for business rule variations.
interface PricingStrategy { double calculate(double base); }
class SeasonalDiscount implements PricingStrategy {
public double calculate(double base) { return base * 0.80; }
}
class LoyaltyDiscount implements PricingStrategy {
public double calculate(double base) { return base * 0.90; }
}
PricingStrategy s = [Link]() ? new LoyaltyDiscount()
: new SeasonalDiscount();
double price = [Link](basePrice);
■ Observer — Publish-subscribe: subjects notify registered observers on state change. Spring
ApplicationEvent/ApplicationListener and Kafka topics are large-scale observer implementations.
■ Template Method — Base class defines algorithm skeleton; subclasses override specific
steps. Spring JdbcTemplate, AbstractBeanFactory all use this — the common logic lives in the
base, customisation in subclasses.
■ Command — Encapsulates a request as an object with execute(). Enables queuing, logging,
undo/redo. PaymentRetryQueue stores ProcessPaymentCommand objects that can be replayed.
■ Chain of Responsibility — Request passed along a chain; each handler decides to process
or pass on. Spring Security's FilterChain is the canonical Java example.
Java & Backend Engineering — Complete Study Guide Page 10
■ State — Object behaviour changes based on internal state. E.g., Order state machine:
CREATED → PAID → PROCESSING → SHIPPED → DELIVERED, each state with its own valid
transitions and behaviour.
Java & Backend Engineering — Complete Study Guide Page 11
■ Section 2 — Spring Ecosystem
2.1 Spring Core — IoC, DI, AOP CORE
Inversion of Control (IoC) Container
IoC inverts traditional control: instead of objects creating their dependencies, the framework creates
and injects them. The ApplicationContext is the IoC container — it manages bean instantiation,
dependency wiring, lifecycle callbacks (@PostConstruct, @PreDestroy), and scope management.
Bean scopes: Singleton (default — one per container, shared), Prototype (new instance per
injection point), Request/Session/Application (web-scoped — one per HTTP request/session/app).
Spring handles circular dependencies for singleton beans only via setter injection (constructor
injection intentionally fails fast on circular deps).
Dependency Injection — Three Styles
■ Constructor Injection (preferred) — Dependencies as constructor parameters. Object
cannot be created without them — guarantees complete initialisation. Enables immutability (final
fields). Easy to test: just call the constructor. No Spring context needed in unit tests.
■ Setter Injection — For optional dependencies. Object can be in an uninitialised state until
setter is called. Use sparingly.
■ Field Injection (@Autowired on field — avoid) — Hides dependencies, prevents final fields,
requires Spring context in unit tests, and violates the explicit dependency principle. Only
acceptable in test classes.
@Service
public class PaymentService {
private final PaymentRepository repo;
private final AuditService audit;
private final KafkaProducerService kafka;
// Single constructor — @Autowired implicit in Spring Boot
public PaymentService(PaymentRepository repo,
AuditService audit,
KafkaProducerService kafka) {
[Link] = repo;
[Link] = audit;
[Link] = kafka;
}
}
Aspect-Oriented Programming (AOP)
Java & Backend Engineering — Complete Study Guide Page 12
AOP separates cross-cutting concerns (logging, security, transactions, caching, metrics) from
business logic. Without AOP, these concerns scatter across every class. AOP centralises them in
Aspects applied automatically via proxies.
■ Aspect — Module encapsulating a cross-cutting concern: AuditAspect, PerformanceAspect.
■ Advice — Action taken: @Before, @After, @AfterReturning, @AfterThrowing, @Around
(most powerful — full control over invocation).
■ Pointcut — Expression defining where advice applies. execution(* [Link]..*(..)) —
all methods in service package.
■ Join Point — Specific execution point intercepted. In Spring AOP: always a method
invocation.
■ Weaving — Applying aspects at runtime via JDK Dynamic Proxy (interface-based) or CGLIB
subclass proxy (class-based).
@Aspect @Component
public class PerformanceAspect {
@Around("@annotation(Monitored)")
public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
long start = [Link]();
try {
Object result = [Link](); // invoke actual method
[Link]("{} OK in {}ms", [Link](),
[Link]() - start);
return result;
} catch (Exception ex) {
[Link]("{} FAILED after {}ms", [Link](),
[Link]() - start);
throw ex;
}
}
}
CRITICAL: AOP proxies only intercept calls that come through the proxy. If method A calls @Transactional
method B in the SAME class, it bypasses the proxy — no transaction is started. Fix: move B to a separate
Spring bean, or use self-injection.
2.2 Spring Boot — Auto-Config, Starters, Actuator CORE
Auto-Configuration
Spring Boot reads
META-INF/spring/[Link]
and applies configuration classes conditionally: @ConditionalOnClass (is this library on the
classpath?), @ConditionalOnMissingBean (has the user provided their own?),
Java & Backend Engineering — Complete Study Guide Page 13
@ConditionalOnProperty (is this property set?). This wires up sensible defaults without manual
XML or @Configuration.
Example: If spring-boot-starter-data-jpa is on the classpath and a DataSource bean exists,
Spring auto-creates LocalContainerEntityManagerFactoryBean, JpaTransactionManager, and
enables @EnableJpaRepositories scanning — you get a fully wired JPA layer with zero
@Configuration.
Starters
Starters are opinionated, curated dependency descriptors. spring-boot-starter-web pulls in
Spring MVC, Jackson, embedded Tomcat, validation. All versions are tested together — no
dependency conflicts. Create custom starters for shared internal libraries: write an auto-configuration
class, add it to [Link] / [Link], publish as a jar.
Actuator — Production Readiness Endpoints
Actuator exposes management endpoints automatically. Key endpoints: /actuator/health
(Kubernetes liveness/readiness probes), /actuator/metrics (Micrometer — all JVM and app
metrics), /actuator/prometheus (Prometheus scrape endpoint), /actuator/info (build info, git
commit SHA), /actuator/loggers (change log levels at runtime without restart), /actuator/env (all
config properties).
# [Link] — secure actuator config for production
management:
endpoints:
web:
exposure:
include: health,metrics,info,prometheus
endpoint:
health:
show-details: when-authorized
probes:
enabled: true # /health/liveness /health/readiness
metrics:
export:
prometheus:
enabled: true
Always secure Actuator endpoints. /env can expose credentials; /beans exposes full application context.
Expose only /health and /info publicly. Protect everything else behind ROLE_ACTUATOR or restrict to
internal network via firewall rules.
2.3 Spring MVC vs Spring WebFlux (Reactive) ADVANCED
Spring MVC — Blocking Servlet Model
Java & Backend Engineering — Complete Study Guide Page 14
Each HTTP request gets a dedicated thread from the servlet container pool (Tomcat default: 200
threads). Thread blocks on every I/O operation (DB query, HTTP call). Simple mental model —
familiar to all Java developers. Best for: traditional CRUD APIs, integrations with blocking libraries
(JDBC, legacy REST clients), teams new to reactive programming.
Spring WebFlux — Non-Blocking Reactive Model
Event-loop model built on Project Reactor + Netty. A small thread pool (2× CPU cores) handles
thousands of concurrent connections. When I/O blocks, the thread is released to handle another
request — remounted when I/O completes. Mono (0 or 1 item), Flux (0 to N items). Best for:
high-concurrency I/O-bound services, streaming, calling many upstream services in parallel.
@RestController
public class OrderController {
// Return single item
@GetMapping("/orders/{id}")
public Mono<Order> getOrder(@PathVariable String id) {
return [Link](id)
.switchIfEmpty([Link](new NotFoundException(id)));
}
// Combine two async calls in parallel
@GetMapping("/dashboard/{userId}")
public Mono<DashboardDTO> dashboard(@PathVariable String userId) {
return [Link](
[Link](userId),
[Link](userId)
).map(t -> new DashboardDTO(t.getT1(), t.getT2()));
}
// Server-Sent Events stream
@GetMapping(value="/orders/live", produces=TEXT_EVENT_STREAM_VALUE)
public Flux<Order> streamOrders() {
return [Link]();
}
}
Never call blocking code inside a reactive pipeline — it blocks the event-loop thread and stalls ALL other
requests on that thread. Use [Link]() for blocking I/O inside reactive flows. With Java 21
virtual threads, Spring MVC now handles high concurrency just as well as WebFlux for most I/O-bound use
cases.
2.4 Spring Data JPA & Hibernate CORE
JPA & Hibernate Internals
Java & Backend Engineering — Complete Study Guide Page 15
Hibernate (default JPA provider in Spring Boot) maintains a PersistenceContext (First-Level Cache)
— an identity map scoped to a transaction. Every entity loaded in a transaction is cached by its
primary key. Changes to persistent entities are auto-detected and flushed to the DB at transaction
commit (dirty checking). The optional Second-Level Cache (Ehcache, Hazelcast, Redis) spans
sessions — for read-mostly, non-critical data.
Entity lifecycle states: Transient (new, not managed) → Persistent (associated with
PersistenceContext — changes tracked) → Detached (session closed — changes not tracked) →
Removed (marked for deletion). Accessing a lazy collection on a Detached entity throws
LazyInitializationException — always load associations inside a transaction.
N+1 Problem — Most Common Performance Issue
Loading a list of N entities and accessing a lazy association on each fires 1 query for the list + N
queries for associations = N+1 queries. At scale (N=1000), this silently degrades performance by
orders of magnitude.
// PROBLEM — fires 1 + N queries
List<Order> orders = [Link](); // 1 query
[Link](o -> [Link]().size()); // N lazy queries
// FIX 1 — JOIN FETCH in JPQL
@Query("SELECT o FROM Order o JOIN FETCH [Link] WHERE [Link] = :id")
List<Order> findWithItems(@Param("id") String id);
// FIX 2 — @EntityGraph
@EntityGraph(attributePaths = {"items", "[Link]"})
List<Order> findByCustomerId(String customerId);
// FIX 3 — Hibernate batch fetching on the collection
@BatchSize(size = 30) // load 30 collections in 1 SQL IN() query
@OneToMany(mappedBy = "order", fetch = [Link])
private List<OrderItem> items;
Flyway / Liquibase & HikariCP
Never use ddl-auto=create-drop in production. Use Flyway (V1__init.sql, V2__add_index.sql)
or Liquibase (XML/YAML changesets) for versioned, auditable schema migrations. Both maintain a
schema_version table and fail fast on conflicts.
HikariCP (Spring Boot default pool): key properties — maximumPoolSize (default 10, often too low),
connectionTimeout (default 30s), idleTimeout. Rule of thumb: pool size ≈ (CPU cores × 2) +
effective_disk_spindles. Too small → thread contention; too large → DB connection exhaustion.
2.5 Spring Security — OAuth2, JWT, RBAC SECURITY
Security Filter Chain
Java & Backend Engineering — Complete Study Guide Page 16
Spring Security intercepts requests via a DelegatingFilterProxy bridging the servlet container to
Spring's SecurityFilterChain. Filters execute in order: CORS → CSRF → Session →
Authentication → Authorization. Each filter handles one concern. The security context
(SecurityContextHolder) stores the Authentication object for the duration of the request thread.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> [Link]()) // stateless API
.sessionManagement(s -> [Link](STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/payments/**").hasAuthority("PAYMENT_WRITE")
.anyRequest().authenticated())
.addFilterBefore(jwtFilter, [Link])
.build();
}
JWT Flow & OAuth2 Resource Server
JWT flow: 1) Client POSTs credentials; 2) Server validates via UserDetailsService; 3) Server signs
JWT with private key (RS256/ES256) and returns access token (15 min) + refresh token; 4) Client
sends Authorization: Bearer ; 5) Custom JwtAuthFilter validates signature (via JWKS endpoint),
expiry, issuer, and audience on every request.
For APIs protected by an external IdP (Keycloak, Auth0, Okta, AWS Cognito): use
spring-boot-starter-oauth2-resource-server. Configure the JWKS URI — Spring fetches
public keys automatically and validates every incoming JWT. Machine-to-machine: use
client_credentials grant. SPAs/mobile: authorization_code + PKCE.
2.6 Spring Batch & Spring Integration ADVANCED
Spring Batch
Framework for reliable large-volume batch processing. Core model: Job → Steps → chunk-oriented:
ItemReader reads N items → ItemProcessor transforms each → ItemWriter persists the chunk
atomically. The JobRepository persists execution metadata (job instance, step execution,
parameters) enabling restartability — a failed job resumes from the last successful checkpoint.
Key features: Fault Tolerance — skip specified exceptions (invalid records), retry on transient
failures (DB timeouts) with configurable backoff; Parallel Steps — run independent steps
concurrently; Partitioned Processing — split a large dataset across multiple worker threads or
remote nodes; Remote Chunking — offload chunk processing to message-driven workers via
Kafka/JMS.
Java & Backend Engineering — Complete Study Guide Page 17
@Bean
public Step processPaymentsStep() {
return [Link]("processPayments")
.<RawPayment, ProcessedPayment>chunk(500)
.reader(kafkaItemReader())
.processor(paymentProcessor())
.writer(jpaItemWriter())
.faultTolerant()
.skip([Link]).skipLimit(100)
.retry([Link]).retryLimit(3)
.taskExecutor(asyncExecutor())
.build();
}
Spring Integration
Implements Enterprise Integration Patterns (EIP). Connects applications via Message Channels,
Transformers, Filters, Routers, Splitters, Aggregators, and Adapters. Adapters connect to FTP,
SFTP, JMS, Kafka, HTTP, JDBC, email, S3. Use for file ingestion pipelines, legacy system bridging,
and ETL flows.
Java & Backend Engineering — Complete Study Guide Page 18
■ Section 3 — Microservices Architecture
3.1 Service Decomposition — DDD & Bounded Contexts PATTERN
Domain-Driven Design Building Blocks
■ Entity — Unique identity persisting through state changes. Two entities with the same data but
different IDs are distinct. E.g., Order (orderId), Customer (customerId).
■ Value Object — Defined entirely by its attributes. No identity. Immutable. Two VOs with same
attributes are equal. E.g., Money(100, USD), Address(street, city). Use Java records for VOs.
■ Aggregate — Cluster of entities and VOs treated as a single transactional unit. One
Aggregate Root controls all access and enforces invariants. E.g., Order aggregate root with
OrderItem child entities.
■ Domain Event — A fact that happened in the domain. Past tense: OrderPlaced,
PaymentProcessed, InventoryReserved. Used for integration between bounded contexts.
Immutable.
■ Repository — Abstraction for aggregate persistence. One repository per aggregate root.
Hides DB technology from domain logic.
■ Domain Service — Business logic that doesn't fit naturally in any single entity or VO. E.g.,
FundsTransferService coordinates two Account aggregates.
Bounded Context
An explicit boundary within which a domain model is consistent. The same word (e.g., 'Customer')
can mean different things in different contexts — Sales context Customer has purchase history;
Support context Customer has tickets. Each context has its own model and ubiquitous language
(vocabulary shared between devs and domain experts).
Context Mapping Patterns: Shared Kernel (two teams share a code subset), Customer-Supplier
(downstream depends on upstream), Anti-Corruption Layer (translation layer — prevents foreign
model leaking in), Open Host Service (publish a well-defined integration protocol), Published
Language (shared event schema — e.g., CloudEvents format).
Each microservice should own exactly one bounded context. Service boundary = aggregate boundary = data
ownership boundary. Never share a database between microservices — it eliminates service autonomy.
3.2 Saga Pattern, CQRS & Event Sourcing ADVANCED
Saga Pattern — Distributed Transactions Without 2PC
Java & Backend Engineering — Complete Study Guide Page 19
Two-Phase Commit (2PC) across microservices requires distributed locks, creates tight coupling, and
is brittle. Sagas replace distributed ACID transactions with a sequence of local transactions, each
publishing events/commands. Failures trigger compensating transactions that undo completed steps.
Choreography-based Saga: Each service listens for events and publishes its own. Fully
decentralised — no single point of failure. Hard to visualise the overall flow. Best for simple 2–3 step
sagas. Orchestration-based Saga: Central Saga Orchestrator sends commands and listens to
replies. Explicit state machine — easy to monitor, test, and debug. Best for complex multi-step
business processes (e.g., order fulfilment with 5+ steps).
// Orchestration Saga — Order Placement
// Step 1: CreateOrder -> OrderCreated
// Step 2: ReserveInventory -> InventoryReserved | InventoryUnavailable
// Step 3: ProcessPayment -> PaymentProcessed | PaymentFailed
// Step 4: ShipOrder -> OrderShipped
// Compensations on failure:
// PaymentFailed -> ReleaseInventory -> CancelOrder
// InventoryUnavailable -> CancelOrder (nothing to release)
// Each local transaction: atomically update DB + write to outbox table
// Outbox poller/CDC publishes to Kafka -> next step's service consumes
CQRS — Command Query Responsibility Segregation
Separate the write model (Commands) from the read model (Queries). Write side: validates business
rules, updates domain model, emits domain events. Uses a normalised relational schema. Read
side: maintains denormalised projections (read models) updated asynchronously via events —
optimised for specific query patterns (no JOINs, pre-aggregated). Read and write sides scale
independently. Trade-off: eventual consistency between write and read sides.
Event Sourcing
Store state as an immutable sequence of domain events rather than current state. Reconstruct
current state by replaying events. Benefits: complete audit trail, time-travel debugging, retroactive
projections, natural fit with CQRS. Challenges: event schema evolution, snapshot management for
long-lived aggregates, harder ad-hoc querying.
WARNING: Event Sourcing adds major complexity. Only adopt when you genuinely need the full audit trail,
replay capability, or temporal queries. Traditional CRUD + domain events is sufficient for most microservices.
3.3 Communication — REST, gRPC, Kafka, GraphQL CORE
REST (Synchronous — Request/Response)
Java & Backend Engineering — Complete Study Guide Page 20
HTTP-based, resource-oriented. Stateless. Define contracts with OpenAPI/Swagger. Best for public
APIs and simple request-response. Drawbacks: tight temporal coupling (both services must be
available), potential cascading failures under load. Mitigate with API Gateway aggregation and
resilience patterns (circuit breaker, retry).
gRPC (Synchronous — High Performance)
Google's RPC framework using HTTP/2 + Protocol Buffers (binary). Benefits: strongly-typed contracts
(.proto files generate client and server code in any language), smaller binary payloads, bidirectional
streaming, HTTP/2 multiplexing. Best for high-throughput internal service-to-service communication,
streaming data.
// [Link]
syntax = "proto3";
service PaymentService {
rpc ProcessPayment (PaymentRequest) returns (PaymentResponse);
rpc StreamTransactions (AccountReq) returns (stream Transaction);
rpc BatchProcess (stream PaymentRequest) returns (BatchResult);
}
message PaymentRequest {
string account_id = 1;
int64 amount_cents = 2;
string currency = 3;
}
Apache Kafka (Asynchronous — Event Streaming)
Distributed, durable event streaming platform. Producers append records to topic partitions
(immutable log). Consumer groups read partitions in parallel — each partition is consumed by exactly
one consumer in the group. Messages are retained durably (configurable — days to forever),
enabling replay.
Transactional Outbox Pattern: write domain event to an outbox table in the SAME DB transaction
as the business data. A CDC tool (Debezium) or a poller reads the outbox and publishes to Kafka.
This eliminates dual-write inconsistency — either both the business data AND the event are
persisted, or neither.
Idempotent Consumers: always design Kafka consumers to handle duplicate message delivery
(network retries, consumer group rebalancing). Use a processed-event-ID table (INSERT IGNORE or
ON CONFLICT DO NOTHING) to deduplicate.
Java & Backend Engineering — Complete Study Guide Page 21
@KafkaListener(topics = "[Link]", groupId = "notification-svc")
public void handle(PaymentProcessedEvent event, Acknowledgment ack) {
if ([Link]([Link]())) {
[Link]();
return;
}
try {
[Link](event);
[Link]([Link]());
[Link]();
} catch (TransientException e) {
// Do NOT ack — will be redelivered by Kafka
} catch (PermanentException e) {
[Link]("[Link]", event);
[Link](); // ack to prevent infinite retry loop
}
}
3.4 Service Mesh, Discovery & Resilience Patterns PATTERN
Service Discovery
Client-side discovery (Netflix Eureka, Spring Cloud): service registers itself in a registry; clients
query the registry and load-balance themselves (Spring Cloud LoadBalancer). Server-side
discovery (Kubernetes Service + DNS, Consul): clients use a stable hostname; the platform routes
to healthy instances. Kubernetes uses server-side discovery natively — a Service DNS name always
routes to healthy pods via kube-proxy.
Service Mesh (Istio / Linkerd)
A sidecar proxy (Envoy in Istio) is injected alongside every pod. ALL network traffic flows through
sidecars. This gives the platform control over: automatic mTLS between all services (zero-trust),
traffic management (canary, A/B, mirroring), circuit breaking, retries, timeouts, rate limiting, and rich
observability — without changing a single line of application code.
Resilience4j Patterns
■ Circuit Breaker — Three states: CLOSED (normal) → OPEN (fails fast when failure rate
exceeds threshold) → HALF-OPEN (test calls). Prevents cascading failures when a downstream
service is degraded. Configured with failureRateThreshold, waitDurationInOpenState,
permittedCallsInHalfOpenState.
■ Retry — Auto-retries on specified exceptions. Configure maxAttempts, waitDuration, backoff
multiplier, and jitter. Only retry idempotent operations — retrying a payment without an
idempotency key causes double charges.
■ Rate Limiter — Limits calls per time period. Prevents overwhelming downstream services.
Token bucket or sliding window algorithm.
Java & Backend Engineering — Complete Study Guide Page 22
■ Bulkhead — Isolates failures by limiting concurrent calls to a downstream service. Thread
pool bulkhead uses a dedicated thread pool; semaphore bulkhead limits concurrent calls.
Prevents one slow upstream from exhausting all threads.
■ TimeLimiter — Fails fast if a call takes longer than a configured threshold. Prevents resource
exhaustion from hanging connections.
@CircuitBreaker(name = "paymentSvc", fallbackMethod = "paymentFallback")
@Retry(name = "paymentSvc")
@TimeLimiter(name = "paymentSvc")
@Bulkhead(name = "paymentSvc")
public CompletableFuture<PaymentResponse> processPayment(PaymentRequest req) {
return [Link](() -> [Link](req));
}
public CompletableFuture<PaymentResponse> paymentFallback(
PaymentRequest req, CallNotPermittedException ex) {
return [Link]([Link](req));
}
Java & Backend Engineering — Complete Study Guide Page 23
■■ Section 4 — Angular & Frontend
4.1 Core Angular — Components, DI, Change Detection CORE
Components, Directives & Pipes
Components are the building blocks. @Component specifies selector, templateUrl, styleUrls, and
changeDetection strategy. Structural directives modify the DOM tree: *ngIf, *ngFor (with trackBy
for performance), *ngSwitch. Attribute directives modify element appearance: ngClass, ngStyle,
custom directives with @HostListener / @HostBinding. Pipes transform displayed values: date |
'dd/MM/yyyy', currency, async, custom pure/impure pipes.
Angular Dependency Injection
Hierarchical injector tree: Root injector (app-wide singletons, providedIn: 'root') → Module
injectors (lazy-loaded modules get their own injector) → Component injectors (new instance per
component subtree). This enables both global shared services and isolated per-component
instances. Use inject() function (Angular 14+) as an alternative to constructor injection.
Change Detection (CD)
[Link] patches all async APIs and triggers a CD cycle after every event — checking every
component in the tree. Default strategy: all components checked on every event — simple but slow
for large trees. OnPush strategy: component checked only when its @Input reference changes, an
event originates within it, or an Observable via async pipe emits. Requires treating all data as
immutable (spread/[Link] instead of mutation). Dramatically reduces CD work.
Lifecycle Hooks
■ ngOnChanges — Before ngOnInit and whenever @Input values change. Receives
SimpleChanges with previousValue and currentValue.
■ ngOnInit — Once after first ngOnChanges. Use for data loading, subscriptions. @Input values
are fully set here.
■ ngAfterViewInit — After the component view and all child views are fully rendered. Safe to
access @ViewChild references here.
■ ngOnDestroy — Before destruction. ALWAYS unsubscribe from Observables, clear timers,
unregister event listeners here to prevent memory leaks.
4.2 State Management — NgRx, Signals, Routing & Forms ADVANCED
NgRx — Redux Pattern
Unidirectional data flow: Component dispatches Action → Reducer (pure function) computes new
State → Store updates → Selectors (memoised projections) deliver new state to templates. Effects
Java & Backend Engineering — Complete Study Guide Page 24
handle side effects (HTTP, routing, localStorage) by intercepting actions and dispatching new
actions. Enables time-travel debugging (Redux DevTools).
// Action
const loadOrders = createAction('[Orders] Load');
const loadOrdersSuccess = createAction('[Orders] Success', props<{orders: Order[]}>());
// Reducer — pure function, never mutates state
const ordersReducer = createReducer(initialState,
on(loadOrdersSuccess, (state, { orders }) =>
({ ...state, orders, loading: false }))
);
// Effect
loadOrders$ = createEffect(() => [Link]$.pipe(
ofType(loadOrders),
switchMap(() => [Link]().pipe(
map(orders => loadOrdersSuccess({ orders })),
catchError(err => of(loadOrdersFailure({ error: err })))
))
));
Angular Signals (v17+)
Fine-grained reactivity primitives. A signal(value) is a wrapper that notifies consumers when its
value changes. computed(() => ...) creates a derived signal (auto-tracks dependencies,
memoised). effect(() => ...) runs side effects when any read signal changes. Unlike [Link],
only affected components update — no full tree CD traversal.
const count = signal(0);
const doubled = computed(() => count() * 2); // auto-tracks count
effect(() => [Link]('count', count())); // runs on change
[Link](5); // set absolute value
[Link](v => v + 1); // derive from current
// In template: {{ doubled() }} — signal called as function
Routing — Lazy Loading, Guards & Resolvers
■ Lazy Loading — Load feature modules on demand: loadChildren: () =>
import('./orders/[Link]'). Reduces initial bundle size significantly.
■ Route Guards — CanActivate (auth check before entering route), CanDeactivate (warn about
unsaved changes), CanLoad (prevent module download for unauthorised users).
■ Resolvers — Pre-fetch required data before route activates. Component gets data via
[Link]. Prevents empty-state flash.
Java & Backend Engineering — Complete Study Guide Page 25
Reactive Forms vs Template-Driven
Reactive Forms (preferred for complex forms): FormGroup/FormControl/FormArray defined in the
component class. Synchronous access to form state. Custom validators are plain functions. Easy to
unit-test (no DOM). Dynamic fields straightforward with FormArray. Template-Driven (simple forms):
logic in template via ngModel two-way binding. Less TypeScript but harder to test and manage
complex validations.
Java & Backend Engineering — Complete Study Guide Page 26
■■ Section 5 — Data & Persistence
5.1 PostgreSQL / MySQL — Advanced SQL CORE
Indexes
B-tree (default): equality (=), range (<, >, BETWEEN), prefix LIKE 'abc%', ORDER BY. Composite
indexes: column order is critical — leftmost prefix rule applies. Place equality conditions first, range
condition last. Covering index: include all queried columns so DB answers from index alone
(index-only scan, no heap access). Partial index: index a subset of rows — CREATE INDEX ON
orders(status) WHERE status='PENDING' — smaller, faster for specific queries.
Query Optimisation
Use EXPLAIN ANALYZE (PostgreSQL) to inspect query plans. Watch for: Sequential Scan on large
tables (missing index), Nested Loop Join on large tables (prefer Hash Join for large datasets), index
not used because a function wraps the column (WHERE YEAR(created_at) = 2024 defeats index on
created_at — use WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01').
Run ANALYZE to update statistics when queries are slow after bulk loads.
Partitioning & Transaction Isolation
Partitioning splits large tables for performance and maintenance. Range partitioning by date
(monthly transaction partitions). List partitioning by region. Hash partitioning for even distribution.
Queries with partition key in WHERE clause trigger partition pruning — only relevant partitions are
scanned.
Isolation levels (least to most isolated): READ UNCOMMITTED (dirty reads — almost never use),
READ COMMITTED (PostgreSQL default — no dirty reads but non-repeatable reads possible),
REPEATABLE READ (MySQL InnoDB default — consistent snapshot within transaction),
SERIALIZABLE (full isolation — prevents phantom reads, slowest).
5.2 NoSQL — MongoDB, Cassandra, Redis ADVANCED
MongoDB — Document Store
BSON (binary JSON) documents in collections. Schema-flexible. Rich query language supporting
nested documents, arrays, aggregation pipeline (project, group, lookup, unwind). Horizontal scaling
via sharding (data distributed across shards based on shard key). Use for: hierarchical/nested data,
flexible schemas, content management, product catalogues. Avoid: when you need strong relational
consistency or complex multi-document transactions frequently.
Apache Cassandra — Wide-Column Store
Java & Backend Engineering — Complete Study Guide Page 27
Designed for massive scale and high availability. Data distributed via consistent hashing based on
partition key. No single point of failure. Tunable consistency: QUORUM (majority of replicas must agree
— strong), ONE (fastest — eventual). No JOINs, no ad-hoc queries. Query-driven data modelling:
design tables around specific access patterns. Create multiple tables for different query patterns
(denormalisation is expected and necessary).
-- Table designed for 'get last 100 transactions for account X'
CREATE TABLE transactions_by_account (
account_id TEXT,
txn_date TIMESTAMP,
txn_id UUID,
amount DECIMAL,
description TEXT,
PRIMARY KEY (account_id, txn_date, txn_id)
) WITH CLUSTERING ORDER BY (txn_date DESC);
SELECT * FROM transactions_by_account
WHERE account_id = 'ACC001' LIMIT 100; -- single partition, very fast
Redis — In-Memory Data Store
All data in memory (~100 000 ops/sec). Rich data structures: Strings (simple cache), Lists (queues,
stacks), Sets (unique tags, intersections), Sorted Sets (leaderboards, priority queues), Hashes
(session storage, object cache), Streams (lightweight Kafka alternative with consumer groups).
Patterns: Cache-aside (check Redis first, load from DB on miss, write back), Rate limiting (INCR
rate:userId:minute + EXPIRE), Distributed lock (SET key value NX EX 30), Pub/Sub (real-time
notifications). Persistence: RDB (point-in-time snapshot — compact, fast restart) + AOF (append
every write — more durable). Use both in production. Redis Cluster for sharding; Redis Sentinel for
HA with auto-failover.
5.3 Elasticsearch — Search & Analytics ADVANCED
Architecture & Core Concepts
Built on Apache Lucene. JSON documents stored in indices. Each index split into primary shards
(horizontal scaling) and replica shards (redundancy + read scaling). Queries distributed across
shards automatically; results merged. Inverted index: for each term, stores a posting list of all
documents containing it — O(1) term lookup. Supports relevance scoring (BM25), fuzzy matching,
phrase queries, prefix/wildcard, aggregations, geospatial.
Use cases: application search (e-commerce, document search), log analytics (ELK/OpenSearch
stack), security analytics (SIEM). Important rule: Elasticsearch is a search index, not a primary
database. Always sync from a primary store (PostgreSQL, MongoDB) to ES via CDC (Debezium) or
Kafka consumers. Never use ES as the system of record.
Java & Backend Engineering — Complete Study Guide Page 28
■■ Section 6 — Cloud & DevOps
6.1 Docker & Kubernetes CLOUD
Docker — Containerisation
An Image is an immutable, layered filesystem built from a Dockerfile. Each instruction creates a
layer (cached independently). A Container is a running image instance — isolated process with its
own filesystem, network namespace, and process tree. Layers are shared between images, reducing
disk and download size.
Multi-stage builds: build stage uses full JDK; final stage copies only the compiled JAR into a minimal
JRE Alpine image. Reduces image size from ~800 MB to ~120 MB and eliminates build tools/source
from production images.
# Optimised multi-stage Spring Boot Dockerfile
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /app
COPY mvnw [Link] ./
COPY .mvn .mvn
RUN ./mvnw dependency:go-offline -q # cache dependencies layer
COPY src ./src
RUN ./mvnw package -DskipTests
FROM eclipse-temurin:21-jre-alpine # minimal JRE only
RUN addgroup -S app && adduser -S app -G app
USER app
COPY --from=builder /app/target/*.jar [Link]
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/[Link]"]
Kubernetes Core Objects
■ Pod — Smallest deployable unit. Containers in a pod share network namespace and volumes.
Rarely created directly — managed by Deployments or StatefulSets.
■ Deployment — Manages stateless apps. Handles rolling updates (maxUnavailable,
maxSurge), rollbacks, and replica counts via ReplicaSet.
■ Service — Stable network endpoint. ClusterIP (internal), NodePort (external on node port),
LoadBalancer (cloud LB), ExternalName (DNS alias).
■ Ingress — HTTP/HTTPS routing to services. Host/path-based routing, TLS termination, rate
limiting, auth (via nginx-ingress or traefik annotations).
■ ConfigMap / Secret — Externalise configuration. ConfigMap for non-sensitive; Secret
(base64-encoded — use external-secrets-operator + Vault in prod) for credentials. Mounted as
env vars or volume files.
Java & Backend Engineering — Complete Study Guide Page 29
■ HPA — Horizontal Pod Autoscaler. Scales pod count based on CPU/memory or custom
Prometheus metrics (via metrics-server + Prometheus Adapter).
■ PodDisruptionBudget — Limits simultaneous pod disruptions during voluntary operations
(node drain, rolling update). Guarantees minimum available replicas.
■ StatefulSet — For stateful apps (databases, Kafka). Stable network identity (pod-0, pod-1...),
ordered deployment, persistent volume per pod.
Always set resource requests (for scheduling) AND limits (for isolation). Without limits, a runaway container
can exhaust node resources and starve other pods. Use LimitRange and ResourceQuota at the namespace
level.
6.2 CI/CD, GitOps & Deployment Strategies CLOUD
CI/CD Pipeline
A production pipeline: Code push → Pull Request → CI: compile, unit tests, integration tests
(TestContainers), code coverage gate, SonarQube analysis, dependency vulnerability scan (Snyk).
On PR merge: build Docker image, scan image (Trivy), push to registry (ECR/GCR), deploy to
staging, run E2E tests. Production deploy: manual approval gate or auto-deploy with feature flags +
canary.
GitOps with ArgoCD
Git is the single source of truth for desired cluster state. Kubernetes manifests or Helm charts live in a
Git repo. ArgoCD continuously watches the repo and syncs the cluster. Manual kubectl changes are
detected as drift and can be auto-corrected. Every change is audited in Git history. Rollback = revert
a commit. Teams use familiar Git workflows (PRs, code review) for infrastructure changes.
Deployment Strategies
■ Rolling Update (K8s default) — Gradually replaces old pods with new. Configurable
maxUnavailable (max old pods down) and maxSurge (max extra new pods). Zero downtime but
mixed versions serve traffic during rollout.
■ Blue/Green — Two identical environments. Switch all traffic from blue to green instantly
(change Service selector). Zero downtime, instant rollback. Requires 2× resources during
switchover.
■ Canary — Route a small % of traffic (e.g., 5%) to the new version. Monitor error rate, latency,
and business metrics. Increase % gradually. Instant rollback if metrics degrade. Requires Istio
VirtualService weights or Argo Rollouts.
■ Feature Flags — Deploy code; control feature activation per user/segment at runtime.
Decouples deployment from release. Tools: LaunchDarkly, Unleash, Flagsmith.
6.3 AWS Services & Terraform (IaC) CLOUD
Java & Backend Engineering — Complete Study Guide Page 30
Key AWS Services
■ ECS / EKS — Container orchestration. ECS is AWS-native and simpler; EKS is managed
Kubernetes (portable, standard tooling). Fargate removes EC2 node management for both.
■ RDS / Aurora — Managed relational DB. Aurora PostgreSQL is PostgreSQL-compatible but
up to 5× faster. Multi-AZ for HA (auto-failover <30s). Read Replicas for read scaling.
■ SQS — Managed message queue. Standard (at-least-once, best-effort order). FIFO
(exactly-once, strict order, lower throughput). Dead-Letter Queues for failed message isolation.
■ SNS — Pub/Sub. Fan-out: one topic → multiple SQS, Lambda, HTTP, email subscribers.
Combine SNS + SQS for fan-out with per-subscriber durability.
■ S3 — Object storage. 11 9s durability. Lifecycle policies (auto-archive to Glacier). Versioning.
Event notifications to Lambda/SQS.
■ Lambda — Serverless compute. Event-driven, pay-per-invocation, scales to zero. Mitigate
cold start with Provisioned Concurrency. Max 15 min timeout.
■ ElastiCache — Managed Redis / Memcached. Redis Cluster mode for sharding. Multi-AZ with
auto-failover.
■ MSK — Amazon Managed Streaming for Kafka. Manages broker provisioning, patching,
replication. IAM-based authentication.
Terraform — Infrastructure as Code
Declarative HCL configuration. terraform plan shows what will change; terraform apply applies
it. State file tracks real-world resources — store remotely in S3 with DynamoDB lock for team use.
Modules encapsulate reusable infrastructure (standard EKS cluster module, VPC module).
Workspaces separate environments (dev/staging/prod) with the same config. Always use terraform
fmt, terraform validate, and tflint in CI.
Java & Backend Engineering — Complete Study Guide Page 31
■ Section 7 — Security
7.1 OAuth2, OIDC, JWT, mTLS & Secrets SECURITY
OAuth2 Grant Types
■ Authorization Code + PKCE — For web apps and SPAs. User redirected to IdP,
authenticates, receives authorization code. Code exchanged for access + refresh tokens. PKCE
prevents code interception for public clients.
■ Client Credentials — Machine-to-machine (M2M). Service authenticates with client_id +
client_secret directly to the token endpoint. No user involved. Use for background jobs and
service-to-service calls.
■ Refresh Token Flow — Short-lived access tokens (15 min) + long-lived refresh tokens (days).
Client exchanges refresh token for new access token without re-authentication. Revoke refresh
tokens to immediately invalidate access.
JWT — Structure, Validation & Security
Three Base64-URL encoded parts: Header (alg: RS256 or ES256 — asymmetric signing preferred
over HS256) . Payload (claims: sub, iss, aud, exp, iat, custom) . Signature. Never trust a JWT
without validating its signature.
Mandatory validations on every request: 1) Verify signature using public key from JWKS endpoint.
2) Check exp (not expired). 3) Check iss (expected issuer). 4) Check aud (this service is the
intended audience). 5) Validate alg header — reject alg: none (algorithm confusion attack). 6)
Never put sensitive data in payload — it is Base64-encoded, not encrypted.
mTLS — Mutual TLS
Standard TLS: only the server presents a certificate. mTLS: both client AND server present X.509
certificates — mutual authentication. Critical for zero-trust service meshes. Istio automates mTLS
between all pods via Envoy sidecar proxies using SPIFFE/SPIRE identity. Without a service mesh:
use cert-manager on Kubernetes to automate certificate issuance and rotation.
Secrets Management
Never store secrets in code, config files, or environment variables in plaintext. Use HashiCorp Vault
(dynamic secrets — generates unique DB credentials per service instance with auto-expiry; full audit
log of every secret access; automatic secret rotation) or AWS Secrets Manager (managed,
integrates natively with RDS for automatic rotation). In Kubernetes: use External Secrets Operator or
Vault Agent Injector to sync secrets into K8s Secrets from Vault at runtime.
7.2 OWASP Top 10, RBAC/ABAC & API Security SECURITY
Java & Backend Engineering — Complete Study Guide Page 32
OWASP Top 10 — Critical Vulnerabilities
■ A01: Broken Access Control (#1 most critical) — User accessing another user's data,
privilege escalation, accessing admin endpoints. Fix: enforce object-level authorisation checks on
every request. Never trust client-supplied IDs — verify ownership server-side.
■ A02: Cryptographic Failures — Sensitive data transmitted unencrypted, weak algorithms
(MD5, SHA1, DES), short key lengths, hardcoded keys. Fix: TLS 1.2+ for transit, AES-256 at rest,
bcrypt/Argon2 for passwords (never MD5/SHA1 for passwords).
■ A03: Injection (SQL, LDAP, OS Command) — User input interpreted as a command. Fix:
parameterised queries / prepared statements always. Use ORM carefully. Validate and sanitise
all inputs. Principle of least privilege on DB accounts.
■ A07: Cross-Site Scripting (XSS) — Attacker injects malicious scripts rendered in other users'
browsers. Fix: output encoding (use framework's built-in escaping), Content-Security-Policy
headers, HTTPOnly + Secure cookie flags.
■ A08: Software & Data Integrity Failures — Insecure deserialisation, unsigned updates,
compromised build pipeline. Fix: verify artifact signatures, dependency scanning (Snyk,
Dependabot, OWASP Dependency-Check), sign Git commits.
■ CSRF — Cross-Site Request Forgery — Tricks authenticated users into unintended
state-changing requests. Fix: SameSite=Strict or Lax cookie attribute eliminates most CSRF. Add
CSRF tokens for legacy cookie-based auth.
RBAC vs ABAC
RBAC (Role-Based): permissions assigned to roles (ADMIN, MANAGER, VIEWER); roles assigned
to users. Simple and widely used. Spring Security hasRole(), @PreAuthorize. Becomes too
coarse-grained at scale.
ABAC (Attribute-Based): fine-grained decisions based on subject attributes ([Link],
[Link]), resource attributes ([Link], [Link]), and
environment (time of day, IP range). Policy: 'MANAGER can READ documents WHERE
[Link] = [Link]'. More flexible, more complex. AWS IAM policies are a
real-world ABAC implementation.
Java & Backend Engineering — Complete Study Guide Page 33
■ Section 8 — Observability & Reliability
8.1 Distributed Tracing — OpenTelemetry, Jaeger, Zipkin CORE
Distributed Tracing Concepts
A trace represents the end-to-end journey of one request across multiple services. Each service
adds one or more spans (units of work). A span records: traceId (same across all services), spanId,
parentSpanId, service name, operation name, start/end timestamps, status code, and custom
attributes. The trace tree visualises where time is spent — invaluable for diagnosing latency spikes in
microservice chains.
OpenTelemetry (CNCF standard) is the vendor-neutral SDK for traces, metrics, and logs. Spring
Boot 3 + Micrometer Tracing integrates OTel automatically. Trace context propagated via W3C
traceparent header. Backends: Jaeger (CNCF, self-hosted), Zipkin, AWS X-Ray, Grafana Tempo.
# [Link] — Spring Boot 3 OTel tracing
management:
tracing:
sampling:
probability: 0.1 # 10% sampling in production
otlp:
tracing:
endpoint: [Link]
# HTTP, Kafka, @Async, RestTemplate, WebClient all auto-instrumented
# Manual span for custom operations:
Span span = [Link]().name("payment-validation").start();
try ([Link] ws = [Link]([Link]())) {
validatePayment(request);
[Link]("[Link]", [Link]().toString());
} finally { [Link](); }
8.2 Metrics — Micrometer, Prometheus, Grafana & SLOs CORE
Micrometer Metric Types
■ Counter — Monotonically increasing count. [Link], [Link].
Never resets. Used to compute rates (Prometheus rate() function).
■ Gauge — Current snapshot value — goes up and down. [Link], [Link],
[Link]. Sampled at scrape time.
■ Timer — Measures event duration + count. Auto-captures p50, p95, p99 percentiles.
[Link] auto-instrumented by Spring MVC.
Java & Backend Engineering — Complete Study Guide Page 34
■ Distribution Summary — Like Timer but for non-time measurements. [Link],
[Link]. Records histogram and percentiles.
RED Method & USE Method
RED Method for services: Rate (req/sec), Error rate (errors/total), Duration (p99 latency). These
three metrics cover the vast majority of service-level issues. Start every Grafana dashboard with
these three panels.
USE Method for infrastructure resources: Utilisation (% of time resource is busy), Saturation (work
queued beyond capacity), Errors (error events). Apply to CPU, memory, disks, network interfaces.
SLI, SLO, SLA & Error Budgets
SLI — measurable metric of service health: proportion of requests completing < 500ms, error rate %.
SLO — target for an SLI: 99.9% of requests complete < 500ms in a rolling 30-day window. SLA —
external contractual commitment (less strict than SLO — the SLO is internal buffer). Error Budget —
allowed unreliability: 99.9% SLO → 0.1% budget → ~43.8 min downtime per month. When budget
exhausted: freeze feature releases, focus all engineering on reliability.
Log Aggregation — ELK / Grafana Loki
ELK: Logstash/Fluentd collects and parses → Elasticsearch indexes and stores → Kibana visualises.
Alternative: Grafana Loki (stores only metadata/labels, not full-text index — much cheaper) +
Promtail (log collector) + Grafana (dashboards). Always use structured JSON logging. Include traceId
in every log line (use MDC). Log at appropriate levels — DEBUG off in production unless
troubleshooting.
Chaos Engineering
Deliberately inject failures to discover system weaknesses before real incidents do. Define steady
state (normal metrics). Inject failure (kill a pod, add network latency, consume CPU). Observe
whether steady state is maintained. Fix weaknesses. Run in staging first; in production with limited
blast radius. Tools: Chaos Monkey (kill instances), Gremlin (controlled blasts), AWS Fault Injection
Simulator, Chaos Mesh (Kubernetes-native).
Java & Backend Engineering — Complete Study Guide Page 35
■■ Section 9 — Architecture & System Design
9.1 Fundamental Architecture Principles ADVANCED
CAP Theorem
In a distributed system you can guarantee at most two of: Consistency (every read returns the latest
write or an error), Availability (every request receives a non-error response), Partition Tolerance
(system operates despite network partitions). Since network partitions are unavoidable, real systems
choose between CP (consistent, may be unavailable — Zookeeper, etcd, HBase) or AP (always
available with potentially stale data — Cassandra, DynamoDB, CouchDB). Relational DBs reading
from primary = CP; reading from replica = AP.
Hexagonal Architecture (Ports & Adapters)
The domain model (business logic) is at the centre with zero dependencies on frameworks or
infrastructure. It communicates via Ports (interfaces): inbound ports (use-case interfaces driven by
controllers/consumers), outbound ports (repository interfaces, notification interfaces implemented by
infrastructure). Adapters implement ports: REST controllers, Kafka consumers, JPA repositories,
SMTP senders. Benefits: swap DB without touching business logic, test domain logic with in-memory
adapters (no Spring context needed), deploy domain independently of infrastructure.
12-Factor App — Key Factors
■ Config in Environment — Credentials, URLs, feature flags in environment variables — not
hardcoded or in version-controlled config files.
■ Stateless Processes — Store all state (sessions, caches) in external services. Any instance
handles any request — enables horizontal scaling without session affinity.
■ Backing Services as Attached Resources — Database, Redis, Kafka accessed via config
URL. Swap local MySQL for RDS by changing one env var — no code change.
■ Disposability — Fast startup (< 5s), graceful shutdown (drain in-flight requests, finish Kafka
offsets, close DB connections). Enables rapid scaling and zero-downtime deploys.
■ Dev/Prod Parity — Same DB engine, same OS, same backing services across environments.
Docker Compose + TestContainers eliminate 'works on my machine'.
■ Logs as Event Streams — Write to stdout only. Infrastructure (Fluentd, CloudWatch, Splunk)
handles collection and routing. App has no logging infrastructure concerns.
9.2 Migration, API Design & Multi-tenancy Patterns PATTERN
Strangler Fig Pattern
Incrementally migrate a monolith to microservices without a big-bang rewrite. An API Gateway or
reverse proxy facade routes requests to either the monolith or the new microservice. New features
Java & Backend Engineering — Complete Study Guide Page 36
are built as microservices. Existing functionality is migrated bounded-context by bounded-context.
Over time the monolith shrinks until it is decommissioned. Allows continuous delivery throughout the
migration and reduces risk.
Anti-Corruption Layer (ACL)
When integrating with a legacy system or poorly-designed upstream, the ACL translates between that
system's domain model and your clean domain model. Prevents legacy concepts, naming
conventions, and data structures from leaking into your codebase. Implemented as a dedicated
translation adapter. If the legacy system changes, only the ACL changes — your domain is protected.
API Versioning Strategies
■ URI versioning (/api/v1/orders) — Most visible, easy to route, cache, and test. Downside:
version number in URL.
■ Header versioning (Accept: application/[Link].v2+json) — Clean URLs, more
semantically correct. Harder to test in browser.
■ Query parameter (/api/orders?version=2) — Flexible, can be omitted accidentally. Used by
Azure REST APIs.
Best practices: never break backward compatibility. Add new optional fields rather than removing or
renaming. Deprecate old versions with warnings and sunset headers. Support at least N-2 versions.
Multi-Tenancy Patterns
■ Silo (separate deployment per tenant) — Full isolation — separate DB, separate
infrastructure. Maximum security and compliance. Highest cost. For enterprise customers with
strict data residency requirements.
■ Bridge (shared infra, separate schema/DB) — One application, one DB server but isolated
schema per tenant (or separate DB). Balance of isolation and cost. Popular with PostgreSQL +
schema-per-tenant + Row Level Security.
■ Pool (shared everything + tenant_id column) — Most cost-efficient. All tenants in the same
tables. Lowest isolation — data leakage risk if access control logic has a bug. Suitable for SMB
SaaS with low sensitivity.
Database Per Service
Each microservice exclusively owns its data store. No direct cross-service DB access — only via the
service's API. Enables polyglot persistence (Order=PostgreSQL, Product=MongoDB, Cart=Redis).
Challenges: no SQL JOINs across services (use API Composition or CQRS projections), distributed
transactions (Saga pattern), eventual consistency. Benefits: independent deployment, independent
scaling, technology freedom, true data encapsulation.
Java & Backend Engineering — Complete Study Guide Page 37
■ Section 10 — Testing Strategy
10.1 Testing Pyramid — Unit, Integration, E2E CORE
The Testing Pyramid
The pyramid shape reflects the ideal distribution: many fast, cheap unit tests at the base; fewer,
slower integration tests in the middle; minimal full E2E tests at the top. Inverting the pyramid (too
many E2E, too few unit tests) leads to slow, brittle test suites that block delivery and provide poor
failure diagnostics.
■ Unit Tests (base — most, fastest) — Single class in complete isolation. All dependencies
mocked. Runs in milliseconds. No Spring context, no DB. JUnit 5 + Mockito. Cover: business
logic, edge cases, error paths, all branches.
■ Integration Tests (middle — moderate) — Test component interactions. @DataJpaTest
(JPA layer only), @WebMvcTest (web layer only), @SpringBootTest (full context). Use
TestContainers for real PostgreSQL, Kafka, Redis — eliminates mocks that don't behave like
reality.
■ E2E Tests (top — few, slowest) — Full user journeys from HTTP request to DB and back.
REST Assured for API E2E. Cypress/Playwright for UI. Run against a deployed staging
environment. Keep minimal — each is expensive and potentially flaky.
TestContainers — Real Infrastructure in Tests
TestContainers starts real Docker containers (PostgreSQL, Kafka, Redis, MongoDB) during test
execution. Catches DB-specific SQL syntax errors, Kafka consumer group behaviour, Redis expiry
handling — issues that in-memory H2 and mocks cannot surface. Spring Boot 3.1+ has first-class
TestContainers support.
Java & Backend Engineering — Complete Study Guide Page 38
@SpringBootTest
@Testcontainers
class PaymentIntegrationTest {
@Container
static PostgreSQLContainer<?> pg =
new PostgreSQLContainer<>("postgres:15-alpine");
@Container
static KafkaContainer kafka =
new KafkaContainer([Link]("confluentinc/cp-kafka:7.4"));
@DynamicPropertySource
static void cfg(DynamicPropertyRegistry reg) {
[Link]("[Link]", pg::getJdbcUrl);
[Link]("[Link]-servers", kafka::getBootstrapServers);
}
@Test
void shouldProcessPaymentAndPublishEvent() {
// Full test with real Postgres + Kafka
}
}
Contract Testing with Pact
Consumer-driven contract testing without needing a shared integration environment. The consumer
defines expected request-response interactions as a Pact contract file. The provider runs verification
tests against all contracts in the Pact Broker. Breaking API changes are caught in CI before
deployment. Enables truly independent service deployment. Particularly valuable in large
organisations where many teams consume each other's APIs.
Performance Testing
■ Load Testing — Simulate expected production traffic. Verify p99 latency SLOs. Tools: JMeter
(GUI + CLI), Gatling (Scala DSL, realistic simulations), k6 (JS, developer-friendly, cloud
integration).
■ Stress Testing — Increase beyond expected load to find the breaking point. Identifies the
weakest link (DB connections, CPU, memory, specific service).
■ Soak Testing — Run at expected load for hours/days. Finds memory leaks, connection pool
exhaustion, disk fill.
■ Spike Testing — Sudden burst of traffic. Tests autoscaling response time and graceful
degradation under sudden overload.
■ Mutation Testing (PIT) — Automatically modifies code (mutants) and verifies tests detect the
change. Measures test suite quality, not just coverage. High coverage with low mutation score =
tests not truly asserting correctness.
Java & Backend Engineering — Complete Study Guide Page 39
■ Section 11 — Soft Skills & Engineering Culture
11.1 System Design Interviews ADVANCED
Structured Approach to System Design
System design interviews assess your ability to think at scale, make engineering trade-offs, and
communicate complex ideas clearly. A structured approach demonstrates breadth, depth, and
professional maturity.
■ Step 1 — Clarify Requirements (5 min) — Functional requirements: what must the system
do? Non-functional: scale (DAU/MAU, QPS), latency SLO (p99), availability (99.9% vs 99.99%),
consistency (strong vs eventual), geographic distribution. Ask — don't assume.
■ Step 2 — Capacity Estimation (5 min) — Back-of-envelope: QPS (DAU × actions/day /
86400), storage (request size × QPS × retention × replication), bandwidth. This informs whether
sharding, caching, or CDN are needed.
■ Step 3 — High-Level Design (10 min) — Major components (API Gateway, services,
databases, caches, message queues), data flow, key APIs. Choose major technologies and
briefly justify. Draw a component diagram.
■ Step 4 — Deep Dive (15-20 min) — Focus on the hardest parts: data model, consistency
guarantees, handling hot partitions, failure scenarios, scaling bottlenecks. Ask which areas to
focus on.
■ Step 5 — Bottlenecks & Trade-offs (5 min) — Identify SPOFs, scaling limits, and trade-offs
made. What changes at 10× scale? What monitoring would you add?
Interviewers evaluate thought process and trade-off analysis more than the 'correct' design. There is rarely
one right answer. Verbalise your reasoning: 'I'm choosing Kafka over RabbitMQ because we need message
replay and consumer group fan-out.' Show you know pros AND cons of every decision.
11.2 Leadership, Code Review, Agile & Estimation CORE
Architecture Decision Records (ADRs)
ADRs capture significant architectural decisions with context, the decision made, and its
consequences. Stored in the Git repository alongside code
(docs/adr/[Link]). When a new engineer asks 'Why Kafka instead of
RabbitMQ?' the ADR answers it without tribal knowledge. Format: Title → Status
(Proposed/Accepted/Deprecated) → Context (forces at play) → Decision → Consequences
(trade-offs). Record decisions NOT made and why.
Code Review Best Practices
Java & Backend Engineering — Complete Study Guide Page 40
■ Review for correctness and maintainability — Not personal style. Automated tools
(Checkstyle, SpotBugs, SonarQube) handle style. Humans focus on: edge cases, security issues,
performance bottlenecks, unclear logic.
■ Specific, actionable feedback — Bad: 'This is wrong'. Good: 'This could NPE if getAddress()
returns null — consider [Link]() here'. Explain the why.
■ Label severity — Blocker: must fix before merge. Nit: minor suggestion, non-blocking.
Question: seeking clarification. Helps authors prioritise.
■ Review promptly — Stale PRs kill velocity. Set a team SLA: first review within 4 business
hours. Prioritise reviewing over starting new work.
■ Be kind and assume good intent — Critique the code, not the person. Ask questions rather
than making accusations. The best reviewers make authors feel supported, not judged.
Technical Leadership & Mentorship
Senior engineers multiply team output. Assign junior engineers meaningful, well-scoped tasks with
clear success criteria and a known escalation path. Pair-program on hard problems — this transfers
tacit knowledge that documentation cannot capture. Write runbooks, architecture diagrams, and
onboarding guides immediately after figuring something out yourself.
Create psychological safety: team members should raise concerns, ask questions, and share
half-formed ideas without fear. The best technical ideas often come from the most junior engineer if
the environment is safe enough for them to speak. Leadership amplifies others' voices — it does not
broadcast only your own.
Agile / Scrum / SAFe
Scrum: 2-week sprints. Ceremonies: Sprint Planning (commit to sprint backlog), Daily Standup
(15-min sync, identify blockers), Sprint Review (demo increment to stakeholders), Retrospective
(continuously improve process). Artifacts: Product Backlog (PO-prioritised), Sprint Backlog
(committed), Increment (shippable). Scrum Master facilitates and removes impediments — not a
project manager.
SAFe (Scaled Agile Framework): scales Agile to enterprise. ART (Agile Release Train): 5–12
teams aligned to a shared cadence and mission. PI (Program Increment) Planning: 2-day all-hands
event where ART teams align plans for the next 10–12 weeks. PI Objectives: measurable business
outcomes each team commits to. Widely adopted in banking and financial services (BofA, JPMC,
Barclays).
Estimation Techniques
Story Points + Planning Poker: relative complexity using Fibonacci (1, 2, 3, 5, 8, 13, 21). Team
votes simultaneously; outliers explain reasoning; converge through discussion. Surfaces hidden
complexity and misaligned understanding. T-shirt sizing (XS/S/M/L/XL): faster for initial backlog
grooming before stories are well-understood. Three-point estimation: Expected = (Optimistic +
4×MostLikely + Pessimistic) / 6. Explicitly acknowledges uncertainty.
Always account for: knowledge gaps (spike needed?), cross-team dependencies, code review
turnaround, testing effort, deployment and rollout time, and unexpected complexity (bugs found
Java & Backend Engineering — Complete Study Guide Page 41
during implementation). Historical velocity from previous sprints is the most reliable input to planning.
Java & Backend Engineering — Complete Study Guide Page 42
Study Guide Complete
All 11 Sections Covered — Core Java · Spring · Microservices · Angular · Data · Cloud · Security
· Observability · Architecture · Testing · Soft Skills
Review Regularly Code Everything
Revisit each section once per week. Active recall Every example shown — type it, run it, break it,
beats passive reading. understand it.
Teach It Back Connect Concepts
Explain concepts out loud. If you can't teach it, you How do Saga + Outbox + Kafka + Idempotency fit
don't know it yet. together? Build the mental map.
Java & Backend Engineering — Complete Study Guide Page 43