Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
  • MockMvc, WebTestClient & Testcontainers in Depth — Interview Questions
  • REST Principles, Status Codes & Resource Design — Interview Questions
  • OpenAPI, Validation Errors, API Versioning & GraphQL — Interview Questions

Build, DevOps & Cloud

  • Maven & Gradle at Scale — Interview Questions
  • Git, CI/CD Pipelines & Release Safety — Interview Questions
Chaturmind
← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
  • MockMvc, WebTestClient & Testcontainers in Depth — Interview Questions
  • REST Principles, Status Codes & Resource Design — Interview Questions
  • OpenAPI, Validation Errors, API Versioning & GraphQL — Interview Questions

Build, DevOps & Cloud

  • Maven & Gradle at Scale — Interview Questions
  • Git, CI/CD Pipelines & Release Safety — Interview Questions
HomeLearnJava Interview PrepJava Interview Prep: 8+ Years (Senior & Lead)Expert Core Java
✓ FreeAdvanced· 15 min read

Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

Expert Core Java design questions — securing third-party plugins now that the Security Manager is gone, default methods and backward compatibility, dynamic proxies and java.lang.reflect.Proxy, implementing deep copies, debugging NullPointerExceptions (helpful NPE messages), equals/hashCode in high-traffic caches, the Reflection API, a reflection-based DI container, designing unextendable classes, final as a design tool, how records and sealed classes change OOP, a payment-method design, and choosing functional interfaces.

Published September 25, 2026


How to use this lesson

These are design and judgement questions. The expected answer isn't a definition: it's a decision with trade-offs, ideally tied to how frameworks you use (Spring, Hibernate, Jackson) rely on the same mechanisms.

Q1. Your application loads plugins from third-party sources. How would you keep the system secure?

Short answer: Treat plugins as untrusted code, and remember that in-process sandboxing is no longer available. The Security Manager was deprecated for removal in Java 17, and permanently disabled in Java 24 (JEP 486), so you can't restrict what code does once it's inside your JVM. Use defence in depth:

  1. Supply chain: accept only signed plugin artifacts (verify their signatures or checksums against a trusted key), from a curated registry. Scan the dependencies (SCA), and review the permissions each plugin requests.
  2. Isolation by process: run untrusted plugins out of process: a separate JVM or container with seccomp, a read-only file system, no network by default, and CPU and memory limits. Talk to it over a narrow API (gRPC or HTTP). Alternatives are WebAssembly runtimes or GraalVM polyglot isolates, which give language-level sandboxes.
  3. For trusted but independent plugins (for example, internal teams): isolate them with separate ClassLoaders or JPMS module layers, to avoid dependency clashes and control the exported API. This is not a security boundary.
  4. A least-privilege API: plugins receive only a narrow SPI or facade, never the database connection or secrets. Validate every input and output crossing the boundary.
  5. Runtime controls: timeouts, kill switches, resource quotas, audit logs, and monitoring for anomalous behaviour.

Common trap: answering "use the SecurityManager and a policy file". That mechanism no longer works on current Java.

Q2. How do default methods affect backward compatibility?

Short answer: They let you add methods to published interfaces without breaking implementers. Existing classes inherit the default body, so they still compile and link. That's how Java 8 added stream(), forEach() and removeIf() to collections. The limits:

  • Behavioural compatibility isn't guaranteed. A default implementation may be wrong or slow for some implementations (removeIf on a custom concurrent collection).
  • Conflicts: if a class implements two interfaces with the same default method, it must override it. A new default can also clash with an existing method with the same signature in an implementing class. The class method wins, but it may have different semantics.
  • Source compatibility vs binary compatibility: already-compiled implementations keep working (binary compatible). Recompiling may surface new conflicts.

Use defaults for genuinely generic behaviour built from the abstract methods, and document them. Don't use them to smuggle state or complex logic into interfaces.

Q3. What are dynamic proxies, and how are they used? What is java.lang.reflect.Proxy for?

Short answer: java.lang.reflect.Proxy generates, at runtime, a class implementing one or more interfaces. Every call on it is routed to an InvocationHandler, which can:

  • add behaviour (logging, metrics, transactions, security, retries, caching);
  • forward the call to a real target;
  • or implement the interface entirely (remote stubs, repositories).

It's the mechanism behind:

  • Spring AOP (JDK proxies, for interface-based beans);
  • Spring Data repositories and Feign/HTTP-interface clients;
  • MyBatis mappers, and RMI or remote stubs.
@SuppressWarnings("unchecked")
static <T> T retrying(T target, Class<T> api, int maxAttempts) {
    return (T) Proxy.newProxyInstance(api.getClassLoader(), new Class<?>[]{api}, (proxy, method, args) -> {
        for (int attempt = 1; ; attempt++) {
            try { return method.invoke(target, args); }
            catch (InvocationTargetException e) {
                if (attempt >= maxAttempts || !(e.getCause() instanceof IOException)) throw e.getCause();
            }
        }
    });
}

Key points to cover:

  • JDK proxies work only on interfaces. Proxying classes requires subclass generation (CGLIB or ByteBuddy), which can't handle final classes or methods.
  • Self-invocation bypasses the proxy.
  • equals, hashCode and toString also go through the handler.

Learn it in depth → Composite & Proxy

Q4. How would you implement a deep copy in Java?

Short answer: Options, from best to worst:

  1. Avoid the need: use immutable objects (records, List.copyOf), which can be shared safely.
  2. Copy constructors or copy() methods that copy each mutable field recursively (and use List.copyOf/new ArrayList<>(...) for collections). This is explicit, fast, and type-safe. Handle cycles with an identity map if the graph has them.
  3. A mapping library (MapStruct generates copy code at compile time).
  4. Serialisation round-trip (Java serialisation, or Jackson to JSON and back): it's generic, but slow, requires everything to be serialisable, loses transient state, and Java serialisation carries security baggage. Keep it for tests or tooling, not hot paths.
public Order deepCopy() {
    Order copy = new Order(this.id, this.customer);                  // Customer is immutable: sharing is fine
    this.lines.forEach(l -> copy.lines.add(new OrderLine(l)));       // mutable children copied
    copy.metadata = new HashMap<>(this.metadata);                    // values immutable (Strings)
    return copy;
}

Common trap: clone() on an object with a List field copies only the reference. Both "copies" share the same list.

Q5. A user clicks a button and gets a NullPointerException. How do you diagnose it, and make such problems easier to debug?

Short answer:

  1. Read the stack trace. Since Java 14 (JEP 358), helpful NPE messages say exactly what was null: Cannot invoke "Customer.getAddress()" because "order.customer" is null. Make sure your logs keep full stack traces, with correlation or trace IDs.
  2. Reproduce it with the same input (from the request logs or trace). Add a regression test.
  3. Fix the cause, not the symptom. Why was it null? A missing database row, an optional field that's absent, a lazy initialisation race, a failed deserialisation, or a missing configuration value. Decide whether null is valid (model it with Optional or a default) or invalid (fail fast with Objects.requireNonNull and a clear message at the boundary).

Preventing NPEs, as a practice:

  • Validate at the edges (Bean Validation on DTOs).
  • Constructor-enforced invariants.
  • Nullness annotations checked by tools: JSpecify @Nullable/@NullMarked, with NullAway or Error Prone. Spring Framework 7 adopts JSpecify.
  • Return empty collections, never null.
  • Optional for optional return values.

Common trap: sprinkling if (x != null) checks everywhere hides bugs. The page silently shows nothing, instead of failing loudly where the real mistake is.

Q6. How does implementing equals() and hashCode() correctly affect a cache in a high-traffic application?

Short answer:

  • Accuracy:
    • Keys that are equal but hash differently (a broken contract) cause cache misses, and duplicate entries that grow memory.
    • Over-broad equals (ignoring a relevant field) causes wrong data to be served, for example user A seeing user B's cached result. That's a security incident.
    • Mutable keys changed after insertion become unreachable: a memory leak.
  • Performance:
    • A poorly distributed hashCode (a constant, or only a low-entropy field) makes buckets collide. Lookups degrade towards O(log n) (treeified) or O(n), CPU spikes, and throughput collapses under load.
    • An expensive hashCode (hashing big collections on every lookup) costs CPU. Cache the hash in immutable keys.

Best practice: use small, immutable key types (records: record PriceKey(String sku, String region, Currency ccy)) that include every dimension the value depends on (tenant, locale, version). Test the contract (EqualsVerifier). For distributed caches (Redis), the serialised key string plays the role of equals: build it deterministically.

Q7. Describe the Java Reflection API and its use cases.

Short answer: Reflection (java.lang.reflect plus Class) lets code inspect and use types at runtime:

  • list fields, methods, constructors, annotations and generic signatures;
  • instantiate classes;
  • invoke methods;
  • read and write fields, including private ones after setAccessible(true), where modules allow it.

Use cases:

  • Frameworks: DI (Spring), ORM (Hibernate), serialisation (Jackson), test runners (JUnit discovering @Test methods), mocking (Mockito), validation, and plugin loading by class name.

The costs:

  • Slower than direct calls (much improved: since Java 18, core reflection is reimplemented on method handles, JEP 416).
  • No compile-time safety.
  • Breaks encapsulation: since Java 16/17, strong encapsulation of JDK internals means deep reflection needs --add-opens, and your own modules must opens packages.
  • Problems with GraalVM native images, which need reachability metadata.

Prefer MethodHandles, annotation processing or code generation where performance or native images matter.

Q8. How would you use reflection to build a simple dependency injection container?

Short answer:

  1. Scan for classes marked with a custom @Component (or register them explicitly).
  2. For each class, choose the constructor (the single one, or the one marked @Inject).
  3. Resolve its parameters recursively from the registry (by type), detecting cycles.
  4. Instantiate it with constructor.newInstance(args).
  5. Cache singletons.
  6. Optionally, inject @Inject fields, and call @PostConstruct methods.
public final class MiniContainer {
    private final Map<Class<?>, Object> singletons = new HashMap<>();
    private final Set<Class<?>> creating = new HashSet<>();
    private final Map<Class<?>, Class<?>> bindings;             // interface -> implementation

    public MiniContainer(Map<Class<?>, Class<?>> bindings) { this.bindings = bindings; }

    @SuppressWarnings("unchecked")
    public synchronized <T> T get(Class<T> type) {
        Class<?> impl = bindings.getOrDefault(type, type);
        Object existing = singletons.get(impl);
        if (existing != null) return (T) existing;
        if (!creating.add(impl)) throw new IllegalStateException("Circular dependency on " + impl.getName());
        try {
            Constructor<?> ctor = Arrays.stream(impl.getDeclaredConstructors())
                    .filter(c -> c.isAnnotationPresent(Inject.class)).findFirst()
                    .orElseGet(() -> impl.getDeclaredConstructors()[0]);
            Object[] args = Arrays.stream(ctor.getParameterTypes()).map(this::get).toArray();
            ctor.setAccessible(true);
            Object instance = ctor.newInstance(args);
            singletons.put(impl, instance);
            return (T) instance;
        } catch (ReflectiveOperationException e) {
            throw new IllegalStateException("Cannot create " + impl.getName(), e);
        } finally {
            creating.remove(impl);
        }
    }
}

Key points to cover:

  • Constructor injection keeps objects immutable, and makes cycles visible.
  • Real containers add scopes, qualifiers, lifecycle callbacks, proxies for AOP, and compile-time alternatives (Dagger, Micronaut, Quarkus ArC), which avoid runtime reflection for startup speed and native images.

Q9. How would you design a class that can't be extended or modified?

Short answer: Make it immutable and non-extendable:

  1. Declare the class final (or give it only private constructors, with static factories).
  2. Make all fields private final.
  3. No setters. "Modifying" operations return new instances (withX(...)).
  4. Defensively copy mutable inputs and outputs (List.copyOf, and copies of Date or arrays).
  5. Don't leak this during construction.

Records give you most of this for free: a final class, private final fields, and accessors. You still need to copy mutable components in the compact constructor.

public record Allocation(String orderId, List<String> skus) {
    public Allocation {
        Objects.requireNonNull(orderId);
        skus = List.copyOf(skus);                 // defensive, immutable copy
    }
    public Allocation withSku(String sku) {
        var copy = new ArrayList<>(skus); copy.add(sku);
        return new Allocation(orderId, copy);
    }
}

Common trap: final alone doesn't make a class unmodifiable. A final class with a public setter, or an exposed mutable list, can still be changed. And "final utility class" is a different goal: a private constructor, and static methods.

Q10. Where does final significantly affect the design of a Java program?

Short answer:

  • Immutability and thread safety: final fields give safe publication guarantees (JMM §17.5). Immutable objects can be shared across threads without locks: value objects, configuration, and messages.
  • Designing for inheritance, or prohibiting it: Effective Java Item 19. Make classes final unless they're deliberately designed and documented for extension. This avoids fragile base classes, and keeps invariants safe (String, Integer and LocalDate are final).
  • Security-sensitive types: a subclass could override validation, or capture data.
  • API evolution: final classes and methods can be changed internally without worrying about subclasses that depend on the implementation.
  • Lambdas and inner classes can only capture effectively final locals, which pushes you towards clearer data flow.
  • Constants (static final) are inlined at compile time. Remember the recompilation trap.
  • Sealed hierarchies (Java 17) sit in between: final leaves in a closed type family.

Key points to cover:

  • final classes can't be proxied by CGLIB. Spring and Hibernate need non-final classes and methods for class-based proxies (JPA entities and @Transactional beans). Kotlin's all-open plugin exists for exactly this reason.

Q11. How have records and sealed classes affected object-oriented programming in Java?

Short answer: They push Java towards data-oriented programming, alongside classic OOP:

  • Records are transparent carriers of immutable data. equals, hashCode, toString and accessors are generated, and the state is the API. They make value objects (Money, IDs, DTOs, events) cheap to write, and encourage immutability. They're final, and can't extend classes, which favours composition.
  • Sealed classes and interfaces create closed hierarchies: the permitted subtypes are known to the compiler. Combined with pattern matching (switch with record patterns, Java 21), you get exhaustive, compiler-checked handling of all variants. These are algebraic data types.
  • Design shift: for a closed set of variants (payment results, AST nodes, commands), you can put the behaviour outside the types in exhaustive switches (easy to add operations), instead of virtual methods on each subtype (easy to add types). That's the expression problem: choose deliberately.
sealed interface PaymentResult permits Approved, Declined, Pending {}
record Approved(String authId) implements PaymentResult {}
record Declined(String reason) implements PaymentResult {}
record Pending(Duration retryAfter) implements PaymentResult {}

String message(PaymentResult r) {
    return switch (r) {                                  // exhaustive: no default needed
        case Approved a -> "Paid (" + a.authId() + ")";
        case Declined(String reason) -> "Declined: " + reason;
        case Pending p -> "Retry in " + p.retryAfter().toSeconds() + "s";
    };
}

Learn it in depth → Sealed Classes

Q12. How would you design a system that supports several payment methods (credit card, PayPal, cryptocurrency) with interfaces and abstract classes?

Short answer:

  • An interface as the contract: PaymentMethod/PaymentProvider, with authorize, capture, refund and supports(method). Callers depend only on this interface (Strategy + DIP).
  • An abstract base class for shared workflow: AbstractPaymentProvider implements the interface with a template method: validate → idempotency check → call the provider (abstract) → map the result → audit and metrics. It contains no provider-specific logic.
  • Concrete providers (CardProvider, PayPalProvider, CryptoProvider) implement only their API calls and error mapping (the Adapter role for each SDK).
  • Selection: a registry or map of beans keyed by method, so adding a method means adding a bean.
  • Cross-cutting concerns: Money value objects (never double), idempotency keys, asynchronous confirmations for crypto (pending states and webhooks), a sealed PaymentResult, retries and circuit breakers per provider, and PCI scope reduction (tokenisation).
public interface PaymentProvider {
    PaymentMethodType type();
    PaymentResult authorize(PaymentRequest request);
    RefundResult refund(RefundRequest request);
}

public abstract class AbstractPaymentProvider implements PaymentProvider {
    @Override public final PaymentResult authorize(PaymentRequest req) {
        validate(req);
        return idempotency.getOrCompute(req.idempotencyKey(), () -> {
            PaymentResult r = doAuthorize(req);            // provider-specific
            audit.record(req, r);
            return r;
        });
    }
    protected abstract PaymentResult doAuthorize(PaymentRequest req);
    protected void validate(PaymentRequest req) { if (req.amount().isNegativeOrZero()) throw new InvalidPaymentException(); }
}

Learn it in depth → Strategy Pattern

Q13. Describe a scenario where a functional interface was the best solution.

Short answer: Whenever you need to pass behaviour consisting of one operation, without a class hierarchy. Real examples:

  • Pluggable business rules: Predicate<Order> isEligibleForFreeShipping, composed with and/or/negate, and configured per region.
  • Retry or resilience wrappers: retry(() -> client.call(), policy) takes a Supplier<T>.
  • Callbacks and event handlers: Consumer<OrderPlaced>, and UI listeners (ActionListener is a functional interface).
  • Strategy with a single method: pricing functions in Map<CustomerTier, UnaryOperator<Money>>.
  • Template-method replacement: jdbcTemplate.query(sql, rowMapper), where RowMapper is functional.
Map<CustomerTier, UnaryOperator<Money>> discount = Map.of(
        CustomerTier.GOLD,   m -> m.times(0.90),
        CustomerTier.SILVER, m -> m.times(0.95),
        CustomerTier.BASIC,  UnaryOperator.identity());
Money finalPrice = discount.get(customer.tier()).apply(basePrice);

Key points to cover:

  • When the behaviour grows (several methods, state or dependencies), promote it to a named class or bean. Lambdas are best for small, stateless logic.
  • Define your own functional interface when the domain deserves a name (FraudRule), or you need checked exceptions.

Follow-up questions this topic invites — and their answers

Q: What replaced the Security Manager for restricting code? A: Nothing inside the JVM. Isolation now comes from the OS and the platform: containers, separate processes, seccomp or AppArmor, VMs, or sandboxed runtimes (WASM). JPMS and class loaders provide modularity, not security.

Q: How do Spring's JDK proxies and CGLIB proxies differ in behaviour? A: A JDK proxy implements only the bean's interfaces, so you must inject by interface. CGLIB subclasses the class, so you can inject by class, but final methods aren't intercepted, and the constructor may run twice in older setups. Spring Boot defaults to CGLIB (proxyTargetClass=true).

Q: What is a MethodHandle, and why is it faster than reflection? A: A typed, directly executable reference to a method, constructor or field (java.lang.invoke). Access is checked once, at lookup, and the JIT can inline calls through constant method handles. It's the foundation of lambdas (invokedynamic), and since Java 18, of core reflection itself.

Q: Can records be JPA entities? A: Not as managed entities. JPA needs mutable, non-final classes with no-arg constructors, for proxies and dirty checking. Records are ideal as DTOs, projections (SELECT new ... or interface-less projections), embeddable-like value objects in some providers, and query results.

Previous

Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions

Next

Class Loading, Bytecode & Object Layout — Interview Questions

AI Tutor

Lesson: Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

Quick actions

AI responses can be inaccurate. Verify critical information.