51. What is Spring Boot Actuator and what does it provide?
- Spring Boot Actuator is a UI component library for building admin dashboards in Spring applications
- Spring Boot Actuator provides production-ready monitoring and management endpoints for Spring Boot applications — exposing health status, metrics, environment info, HTTP traffic, beans, thread dumps, and more via HTTP or JMX
- Spring Boot Actuator is a build automation tool similar to Maven or Gradle for Spring projects
- Actuator is only for testing purposes and must be disabled before deploying to production
Answer : B Explanation: Spring Boot Actuator adds “production-ready” features to Spring Boot applications without requiring manual implementation. Add to project: spring-boot-starter-actuator. Key endpoints (accessible at /actuator/): /actuator/health — shows application health status. Customizable: includes database connectivity, disk space, Redis. Returns UP/DOWN/OUT_OF_SERVICE. /actuator/metrics — exposes metrics (JVM memory, CPU usage, HTTP request count and duration, custom metrics). /actuator/info — application info (version, description — configured in application.properties). /actuator/env — environment variables and configuration properties. /actuator/beans — all Spring beans in the context. /actuator/loggers — view and change log levels at runtime. /actuator/httptrace — recent HTTP request/response traces. /actuator/threaddump — thread dump for debugging. /actuator/heapdump — download heap dump. Security: most endpoints are disabled by default (except /health and /info). Enable selectively: management.endpoints.web.exposure.include=health,info,metrics. Protect with Spring Security. Integration: Actuator metrics integrate with Micrometer for export to Prometheus, Datadog, Grafana. Essential for DevOps and production monitoring of Spring Boot applications.
52. What is Java Multithreading and what are the ways to create threads?
- Java multithreading is the ability to run multiple Java applications simultaneously on one machine
- Java multithreading allows concurrent execution of multiple threads within a single program — threads can be created by extending the Thread class, implementing the Runnable interface (preferred), or using Callable with ExecutorService
- Java threads are created by the operating system — Java developers cannot control thread creation
- Multithreading in Java requires special hardware and is only available on multi-core processors
Answer : B Explanation: Java Multithreading enables concurrent execution within one JVM. Thread creation methods: 1. Extending Thread class: class MyThread extends Thread { public void run() { … } } new MyThread().start(). Limitation: Java has single inheritance — cannot extend another class. 2. Implementing Runnable (preferred): class MyTask implements Runnable { public void run() { … } } new Thread(new MyTask()).start(). Allows extending another class. Clean separation between task (Runnable) and thread mechanism. Lambda: new Thread(() -> { … }).start(). 3. Callable with ExecutorService (modern): Future<String> future = executor.submit(() -> { return “result”; }). Returns a value, can throw checked exceptions — unlike Runnable. Thread states: NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED. Key thread methods: start() (creates new thread, calls run()), sleep(long ms) (pauses thread), join() (waits for thread to complete), interrupt() (requests thread interruption), synchronized (mutual exclusion). Modern approach: prefer ExecutorService over raw Thread creation for better resource management and thread pooling.
53. What is the difference between synchronized, volatile, and atomic variables in Java?
- synchronized, volatile, and atomic are all keywords for declaring thread-safe variables
- synchronized ensures mutual exclusion for a code block/method; volatile ensures visibility of variable changes across threads (no mutual exclusion); AtomicInteger/AtomicReference provide lock-free thread-safe operations using CAS (Compare-And-Swap) hardware instructions
- volatile and synchronized are identical — atomic variables are only for advanced users
- These three concepts only apply to static variables and have no effect on instance variables
Answer : B Explanation: Three tools for thread safety in Java: synchronized: ensures only one thread executes a synchronized block/method at a time. Provides mutual exclusion AND visibility. Heavy — involves acquiring/releasing lock, context switches. Use for: complex operations needing atomicity, multiple variable updates. volatile: guarantees all threads see the latest write to the variable (cache coherence). Does NOT provide mutual exclusion — does NOT prevent race conditions for read-modify-write operations. Use for: simple flags (boolean running = false) that one thread writes and others read. Example: volatile boolean isRunning = true; — other threads see updates immediately. Atomic variables (java.util.concurrent.atomic): AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference. Lock-free thread safety using CAS (Compare-And-Swap) hardware instruction. Excellent performance for counters. Example: AtomicInteger counter = new AtomicInteger(); counter.incrementAndGet(). When to use: volatile — simple flags, stopping threads. AtomicXxx — counters, simple state without locks (better performance). synchronized — complex multi-step operations requiring atomicity. java.util.concurrent.locks.ReentrantLock — advanced locking with tryLock(), fair ordering. Modern recommendation: prefer java.util.concurrent utilities over manual synchronized blocks.
54. What is the Java ExecutorService and why is it preferred over raw Thread creation?
- ExecutorService is a Java class for executing system-level commands similar to Runtime.exec()
- ExecutorService is a high-level API for managing a pool of threads — preferred because it reuses threads (avoids expensive thread creation/destruction), manages thread lifecycle, provides Future for async results, and prevents resource exhaustion from unbounded thread creation
- ExecutorService is only for single-threaded sequential task execution in Java applications
- ExecutorService was deprecated in Java 11 and should no longer be used in modern applications
Answer : B Explanation: ExecutorService (java.util.concurrent) manages thread pools efficiently. Creating thread pools (Executors factory): Executors.newFixedThreadPool(4) — pool of exactly 4 threads, queue for excess tasks. Executors.newCachedThreadPool() — grows as needed, reuses idle threads, shrinks when not needed. Executors.newSingleThreadExecutor() — single thread, sequential execution. Executors.newScheduledThreadPool(2) — for delayed and periodic tasks. Submitting tasks: executor.execute(runnable) — fire and forget. Future<T> future = executor.submit(callable) — returns result asynchronously. future.get() — blocks until result is available. future.get(5, TimeUnit.SECONDS) — blocks with timeout. Shutdown: executor.shutdown() — waits for running tasks to complete. executor.shutdownNow() — attempts to stop all tasks. Best practice: Always shut down executor to prevent resource leaks. CompletableFuture (Java 8+): more powerful async API. supplyAsync(), thenApply(), thenCombine(), exceptionally(). Virtual Threads (Java 21 Project Loom): lightweight threads that make thread-per-request model practical at scale. Why avoid raw threads: expensive to create (stack allocation), no return value, no exception handling, unbounded creation can crash the JVM.
55. What is the Java Collections Framework and what are its key interfaces?
- The Java Collections Framework is only for storing primitive data types like int and double
- The Java Collections Framework provides a unified architecture for storing and manipulating groups of objects — with key interfaces including List (ordered, allows duplicates), Set (unordered, no duplicates), Map (key-value pairs), and Queue (FIFO ordering)
- Java Collections are thread-safe by default — no additional synchronization is needed
- The Collections Framework only supports single data type storage — no mixed types in one collection
Answer : B Explanation: The Java Collections Framework (java.util) provides: List interface: ordered, allows duplicates, index-based access. ArrayList — dynamic array, O(1) access, O(n) insert/delete (shifting). LinkedList — doubly linked list, O(1) insert/delete (with iterator), O(n) access. Vector — thread-safe ArrayList (synchronized — deprecated in favor of concurrent alternatives). Set interface: no duplicates, uses equals() and hashCode(). HashSet — O(1) add/contains/remove, no ordering. LinkedHashSet — maintains insertion order. TreeSet — sorted, O(log n), implements SortedSet. Map interface: key-value pairs, keys must be unique. HashMap — O(1) operations, no ordering, allows null key/values. LinkedHashMap — maintains insertion order. TreeMap — sorted by keys, O(log n). Hashtable — legacy thread-safe version (avoid — use ConcurrentHashMap). Queue interface: FIFO ordering. LinkedList implements Queue. PriorityQueue — min-heap based. ArrayDeque — fast double-ended queue. Concurrent collections: ConcurrentHashMap (thread-safe HashMap), CopyOnWriteArrayList (thread-safe ArrayList for reads), BlockingQueue (producer-consumer). Deque: addFirst/addLast — implements both Stack and Queue behavior.
56. What is the difference between ArrayList and LinkedList in Java?
- ArrayList can only store numbers; LinkedList can only store String values
- ArrayList is backed by a dynamic array with O(1) random access but O(n) insert/delete at arbitrary positions; LinkedList is a doubly linked list with O(1) insert/delete at known positions but O(n) random access
- LinkedList is always faster than ArrayList for all operations regardless of use case
- ArrayList and LinkedList are identical — the only difference is their package location
Answer : B Explanation: ArrayList vs LinkedList is a classic Java interview question: ArrayList: backed by Object[]. get(index) — O(1) direct array access. add(element) at end — O(1) amortized (occasional array resize). add(index, element) — O(n) because elements must shift. remove(index) — O(n) shifting. Memory: compact, cache-friendly, stores only the elements. Good for: frequent random access, iteration. LinkedList: doubly linked list (each node stores element, previous reference, next reference). get(index) — O(n) must traverse from head. add/remove at head or tail — O(1). add/remove with ListIterator at current position — O(1). Memory: extra overhead for two pointers per node — ~3× memory of ArrayList for same data. Good for: frequent insertion/deletion at beginning/middle when you have an iterator positioned there. Implements Deque — can be used as stack or queue. In practice: ArrayList is almost always the better choice. LinkedList’s O(1) insert/delete advantage is rarely realized because you usually need to traverse to find the position (O(n)) first. ArrayDeque is preferred over LinkedList when you need a deque/queue/stack — it’s faster and more memory-efficient. Use ArrayList by default.
57. What is the difference between HashMap and ConcurrentHashMap in Java?
- ConcurrentHashMap can store more entries than HashMap due to its segmented storage model
- HashMap is not thread-safe (concurrent modifications can cause data corruption or infinite loops); ConcurrentHashMap provides thread-safe concurrent access using fine-grained segment locking (Java 7) or CAS-based synchronization (Java 8+) without locking the entire map
- ConcurrentHashMap is slower than a synchronized HashMap for all types of operations
- The only difference is naming — both provide identical thread safety guarantees
Answer : B Explanation: HashMap Thread Safety Issue: concurrent reads + writes without synchronization cause: infinite loops (in Java 7, during resize rehashing), data corruption, stale reads, ArrayIndexOutOfBoundsException. Never use HashMap in a multithreaded context without external synchronization. ConcurrentHashMap (java.util.concurrent): Java 7: divided into 16 segments, each with its own lock — 16 threads can write concurrently to different segments. Java 8+: uses CAS (Compare-And-Swap) for most operations — only locks individual buckets when needed (much more concurrent). Guarantees: reads never block (even during concurrent writes). Writes use fine-grained locking. Iterators are weakly consistent (reflect state at some point during iteration). Does NOT allow null keys or null values (unlike HashMap). Alternatives: Collections.synchronizedMap(new HashMap<>()) — wraps with synchronized, locks entire map for every operation — much slower than ConcurrentHashMap. HashTable — legacy synchronized version, lock on every method — avoid. When to use ConcurrentHashMap: any multithreaded code needing a shared map. ConcurrentHashMap is the go-to thread-safe map in Java. CopyOnWriteArrayList: for lists where reads dominate and writes are rare.
58. What are Java Generics and why are they important?
- Java Generics are a way to write code that only works for generic (simple) data types
- Java Generics enable writing type-safe, reusable code by parameterizing types at compile time — replacing Object casts, providing compile-time type checking, and eliminating ClassCastException at runtime
- Java Generics were introduced to allow the same code to run on multiple operating systems
- Generics only apply to Collections — they cannot be used with custom user-defined classes
Answer : B Explanation: Java Generics (introduced Java 5) solve the problem of type-unsafe container classes. Before Generics: List list = new ArrayList(); list.add(“hello”); String s = (String) list.get(0); // explicit cast. If wrong type added → ClassCastException at runtime (too late!). With Generics: List<String> list = new ArrayList<>(); list.add(“hello”); String s = list.get(0); // no cast needed. Wrong type → compile-time error (caught early!). Generic class: class Box<T> { private T content; public T getContent() { return content; } }. Generic method: <T extends Comparable<T>> T max(T a, T b) { return a.compareTo(b) > 0 ? a : b; }. Bounded wildcards: <? extends Number> — any Number subtype (read-only from collection). <? super Integer> — any Integer supertype (write to collection). PECS (Producer Extends, Consumer Super): use extends when reading, super when writing. Type Erasure: generics are a compile-time feature only — at runtime, type information is erased. List<String> and List<Integer> are both List at runtime. Cannot create generic arrays: new T[] (not allowed). Cannot use primitives as type parameters: List<int> (invalid) — use List<Integer>.
59. What are Java 8 Streams and what are their key operations?
- Java 8 Streams are the same as I/O streams (FileInputStream) used for file operations
- Java 8 Streams are a functional API for processing sequences of elements — supporting lazy evaluation, pipeline operations (filter, map, reduce, collect), and parallel processing without modifying the source collection
- Java Streams permanently modify the underlying collection they are created from
- Java Streams can only be used with numeric data types — not with objects or strings
Answer : B Explanation: Java 8 Streams provide declarative, functional-style data processing. Creating streams: collection.stream(), Arrays.stream(array), Stream.of(values), IntStream.range(0, 10). Intermediate operations (lazy — not executed until terminal): filter(Predicate) — keep elements matching condition. map(Function) — transform each element. flatMap(Function) — flatten nested streams. sorted(Comparator) — sort elements. distinct() — remove duplicates. limit(n) / skip(n) — pagination. peek(Consumer) — for debugging. Terminal operations (trigger execution): collect(Collectors.toList()/toSet()/toMap()) — gather results. forEach(Consumer) — perform action for each element. reduce(BinaryOperator) — aggregate to single value. count() — count elements. findFirst() / findAny() — get element (returns Optional). anyMatch / allMatch / noneMatch — boolean predicates. min / max — find extremes. Example: List<String> result = people.stream().filter(p -> p.getAge() > 18).map(Person::getName).sorted().collect(Collectors.toList()). Parallel streams: people.parallelStream() — uses ForkJoinPool, good for CPU-intensive tasks on large datasets. Use with caution — ordering, statefulness issues. Optional: wraps potentially null values — from findFirst(), max() etc. Prevents NullPointerException — use isPresent(), orElse(), ifPresent().
60. What are Lambda Expressions in Java and how do they work with Functional Interfaces?
- Lambda expressions are a way to create anonymous classes with multiple methods
- Lambda expressions are concise anonymous function implementations — they can only be used where a Functional Interface (an interface with exactly one abstract method) is expected, replacing verbose anonymous class syntax
- Lambda expressions require special @Lambda annotation to be declared in Java code
- Lambdas in Java work only with built-in Java types and cannot work with user-defined classes
Answer : B Explanation: Lambda expressions (Java 8) drastically reduce boilerplate for implementing single-method interfaces. Syntax: (parameters) -> expression or (parameters) -> { statements; }. Before lambda: button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println(“clicked”); } }). With lambda: button.addActionListener(e -> System.out.println(“clicked”)). Functional Interface: an interface with exactly ONE abstract method. @FunctionalInterface annotation (optional but recommended for documentation). Built-in functional interfaces (java.util.function): Predicate<T> — boolean test(T t). test(x -> x > 0). Function<T,R> — R apply(T t). apply(String::length). Consumer<T> — void accept(T t). accept(System.out::println). Supplier<T> — T get(). get(() -> new Random().nextInt()). BiFunction<T,U,R> — apply two args. Method references: shorthand for lambdas. String::toUpperCase is equivalent to s -> s.toUpperCase(). Types: Static: Class::staticMethod. Instance: object::instanceMethod. Constructor: Class::new. Lambdas and functional interfaces are the foundation of Java’s functional programming support — used extensively with Streams API, CompletableFuture, and Spring’s event handling.
