Thursday, 17 July 2025

Solving the N+1 Query Problem in REST APIs: A Real-World Banking Example (Made Easy for Spring Boot Developers)

If you're using Spring Boot with Hibernate (JPA) and a relational database like MySQL/PostgreSQL, the N+1 Query Problem can silently hurt your API performance.

Let’s understand this issue in simple steps using a real-world banking project example, and how to fix it using Spring Boot tricks.


๐Ÿ“Œ What is the N+1 Problem?

The N+1 problem occurs when:

  • 1 SQL query fetches the main (parent) data — for example, bank accounts.

  • Then N additional SQL queries are executed for related (child) data — like each account’s transactions.

Expected: 1 smart query to fetch everything. ❌ Actual: 1 query for accounts + N queries for transactions = N+1 total queries.

This increases database load and slows down your REST APIs.


๐Ÿฆ Spring Boot Banking API Example

Say you build a banking dashboard endpoint:

@GetMapping("/api/accounts")
public List<AccountDTO> getAllAccounts() {
    List<Account> accounts = accountRepository.findAll();
    return accounts.stream()
        .map(account -> new AccountDTO(account.getId(), account.getTransactions()))
        .collect(Collectors.toList());
}

Behind the scenes:

  • findAll() runs: SELECT * FROM account;

  • Then for each account, Hibernate runs: SELECT * FROM transaction WHERE account_id = ?;

➡️ With 100 accounts, this results in 101 queries. That’s the N+1 problem.


Why Is It a Big Deal?

  • ๐Ÿšซ Bad for performance: 100 queries slow down response time.

  • ๐Ÿ”ฅ Heavy DB load: Affects scaling when users grow.

  • ๐Ÿงช Hard to debug: Looks fine in code, but performs poorly in production.


How to Fix N+1 in Spring Boot (Step-by-Step)

✅ 1. Use JOIN FETCH in JPA Query

@Query("SELECT a FROM Account a JOIN FETCH a.transactions")
List<Account> findAllWithTransactions();

✅ Fetches everything in 1 query.

✅ 2. Use JPA Entity Graphs

@EntityGraph(attributePaths = {"transactions"})
List<Account> findAll();

✅ Cleaner and better for dynamic loading.

✅ 3. Use DTO Projection (Recommended for REST)

@Query("SELECT new com.bank.dto.AccountDTO(a.id, t) 
FROM Account a JOIN a.transactions t")
List<AccountDTO> getAccountDTOs();

✅ Loads only the required data — no extra queries.

✅ 4. Use Batch Fetching (Optional)

In application.properties:

spring.jpa.properties.hibernate.default_batch_fetch_size=100

✅ Tells Hibernate to fetch child records in batches.


๐Ÿ’ก Best Practices & Tricky Interview Points

  • ๐Ÿงต Lazy vs Eager:

    • Use LAZY loading to avoid unnecessary data.

    • Use JOIN FETCH when you really need the related data.

  • ๐Ÿ›ก️ Avoid LazyInitializationException:

    • Use @Transactional in service layer if accessing lazy fields.

  • ๐Ÿงช Testing:

    • Use Hibernate query counters in tests to catch N+1 bugs.

    • Enable SQL logging: spring.jpa.show-sql=true

  • ๐Ÿง  Interview Tip:

    • Be ready to explain how you identify and fix N+1 using Spring Boot tools.


๐Ÿš€ Summary: Fixing N+1 in Spring Boot

Problem Fix
Too many queries Use JOIN FETCH
Lazy loading issues Use EntityGraph or @Transactional
Poor API performance Use DTO projections
Query explosion Use batch fetch settings

๐Ÿง  Final Thought

"The fastest code isn’t the one that runs first — it’s the one that hits the DB the least.”

If you're building REST APIs for enterprise, finance, or dashboards — fixing N+1 makes your system faster, lighter, and scalable.

Happy Learning :) 


Sunday, 13 July 2025

JWT Interview Q & A asked in MNC company

 

๐Ÿ” 1. How does Spring Security integrate with JWT-based authentication?

Answer:
Spring Security provides a flexible filter chain where you can plug in your own OncePerRequestFilter (e.g., JwtAuthenticationFilter) to intercept incoming requests and:

  • Extract the JWT from the Authorization header

  • Validate it

  • Set the Authentication object in SecurityContextHolder

➡️ This allows stateless authentication using JWT.


๐Ÿ” 2. Why do we use OncePerRequestFilter in JWT security setup?

Answer:
OncePerRequestFilter ensures your filter runs only once per request, making it ideal for:

  • Checking JWT

  • Verifying token signature

  • Setting authentication context

If used wrongly, it can cause repeated token processing.


๐Ÿ” 3. Can we disable session creation in Spring Security for JWT?

Answer:
Yes. JWT is stateless, so we must disable session creation:


http.sessionManagement()

    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);


Without this, Spring may still create sessions and defeat the purpose of using JWT.


๐Ÿ” 4. Where do you validate the JWT token in Spring Boot?

Answer:
In a custom JWT filter, before the request reaches the controller:

  • You extract the token from Authorization header

  • Validate the signature and expiration

  • Fetch user details (optional)

  • Set Authentication in SecurityContext


๐Ÿ” 5. What happens if the JWT token is expired?

Answer:
When a JWT token is expired, it throws a specific exception — most commonly:


io.jsonwebtoken.ExpiredJwtException


In your custom JWT filter (usually extending OncePerRequestFilter), you should catch this exception and return a proper HTTP response:


try {

    // validate token logic

} catch (ExpiredJwtException e) {

    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

    response.getWriter().write("Token expired. Please login again or use refresh token.");

    return;

}


๐Ÿ“Œ HTTP Response:


401 Unauthorized  

Body: Token expired. Please login again or use refresh token.


๐Ÿ”„ Best Practice: Use a refresh token mechanism so that expired tokens can be refreshed without forcing the user to re-login every time.


๐Ÿ” 6. How do you handle JWT in microservices architecture with Spring Security?

Answer:
In microservices, the best approach is to delegate all authentication to a central Auth Service. Here's the flow:

  1. User logs in via Auth Service → receives access token and refresh token.

  2. Access token is then passed in the Authorization: Bearer <token> header for every request to other services.

  3. Each service:

    • Validates the token using a shared secret or public key (if using asymmetric signing like RS256).

    • Authorizes the request based on roles in token claims.

  4. No session state is stored; each service is stateless.

Optional best practice:

  • Use Spring Cloud Gateway or an API Gateway to centralize token validation so individual services don't have to repeat it.


๐Ÿ” 7. Is it safe to keep user roles in JWT?

Answer:
Yes — if the JWT is signed properly using a secure algorithm (HS256, RS256) and a strong secret/private key.

You can embed roles in the JWT payload like this:


{

  "sub": "user1",

  "roles": ["ROLE_ADMIN", "ROLE_USER"],

  "exp": 1723489234

}


๐Ÿ“Œ In your Spring Boot filter, you extract these roles and convert them to GrantedAuthority:


UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(

    username, null, authoritiesFromToken

);


⚠️ Do not trust claims blindly if the signature is invalid or token is tampered.

✅ Signed tokens = safe to use for roles
❌ Unsigned (alg: none) or weak key = huge security risk


๐Ÿ” 8. What is the best way to store JWT tokens on the frontend?

Answer:

  • Access token: HttpOnly Secure Cookie (to avoid XSS)

  • Refresh token: also in HttpOnly cookie or encrypted local storage

❌ Avoid storing in localStorage or sessionStorage — vulnerable to XSS attacks.


๐Ÿ” 9. Can we use Spring Security with both Session and JWT?

Answer:
Not advisable. Choose one:

  • Use Session + CSRF for monoliths or UI-driven apps

  • Use JWT (stateless) for REST APIs and microservices

Mixing both often leads to confusion and security holes.


๐Ÿ” 10. How do you implement role-based access with JWT + Spring Security?

Answer:

  1. Encode roles into JWT claims.

  2. In your filter, extract and set GrantedAuthority in Authentication.

  3. Then use:

@PreAuthorize("hasRole('ADMIN')")


or


http.authorizeHttpRequests().requestMatchers("/admin/**").hasRole("ADMIN")


✅ Don’t forget to prefix roles with "ROLE_".


Wednesday, 16 April 2025

๐Ÿ”ฎ Understanding Future in Java (With Simple Example)

 In Java, working with multithreading and asynchronous programming is made much easier with the help of the java.util.concurrent package. One of the key components in this package is the Future interface.

If you’ve ever needed to run a task in the background and retrieve the result once it's done — without blocking your entire program — then Future is your friend.


๐Ÿš€ What is Future?

A Future represents the result of an asynchronous computation. It acts like a placeholder for a value that will eventually be available after a long-running task completes.

In simple terms, when you submit a task to a thread pool (using an ExecutorService), instead of immediately getting the result, you get a Future object that promises to hold the result once the task finishes.


๐Ÿงต Why Use Future?

  • Perform background operations without blocking the main thread.

  • Check if a task is done with .isDone().

  • Cancel a running task with .cancel(true).

  • Get the result later using .get() (blocks if the result isn't ready).


๐Ÿง‘‍๐Ÿ’ป Basic Example: Using Future with Callable

Here’s a beginner-friendly example showing how to use Future in Java:


import java.util.concurrent.*; public class FutureExample { public static void main(String[] args) throws Exception { // Step 1: Create a thread pool (ExecutorService) ExecutorService executor =
Executors.newSingleThreadExecutor(); // Step 2: Submit a Callable task to the executor Future<Integer> future = executor.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { // Simulate long-running task (e.g., network call, calculation) Thread.sleep(2000); // 2 seconds delay return 10 + 20; } }); System.out.println("Task submitted... Doing other work while waiting"); // Step 3: Do something else while the task runs // Step 4: Get the result (this will block if the task is not finished) Integer result = future.get(); // Step 5: Print the result System.out.println("Result from background task: " + result); // Step 6: Shutdown the executor executor.shutdown(); } }

๐Ÿ–จ️ Output:


Task submitted... Doing other work while waiting Result from background task: 30

๐Ÿ” Explanation:

ExecutorService executor = Executors.newSingleThreadExecutor();

Creates a thread pool with a single thread that will run the submitted tasks.

Future<Integer> future = executor.submit(...)

Submits a Callable task (which returns a value), and returns a Future object.

future.get();

Waits for the task to complete and returns the result. If the task isn’t done yet, it blocks the current thread until it is.

executor.shutdown();

Always shut down the executor to avoid memory leaks or keeping the app running unnecessarily.


๐Ÿ“Œ Additional Tips

  • future.isDone() → returns true if the task is completed.

  • future.cancel(true) → cancels the task if it's not finished yet.

  • Callable<V> vs Runnable: use Callable if you need to return a value or throw exceptions.


๐Ÿค” When Should You Use Future?

Use Future when you:

  • Need to perform background tasks (like file I/O or API calls).

  • Want to run multiple tasks in parallel and wait for results.

  • Want the ability to cancel a task if it’s taking too long.


๐Ÿ’ก Bonus: Want More Power? Try CompletableFuture

If you’re looking for even more flexibility (like chaining tasks, handling errors, async pipelines), check out CompletableFuture. It’s the modern alternative introduced in Java 8.

Monday, 7 April 2025

๐ŸŽฏ Tricky Java 8 map() vs flatMap() Questions

๐Ÿ”ฅ Q1: What's the output of this?


List<String> words = Arrays.asList("Java", "Spring"); List<String> result = words.stream() .map(word -> word.split("")) .flatMap(Arrays::stream) .distinct() .collect(Collectors.toList()); System.out.println(result);

Answer:

Output:


[J, a, v, S, p, r, i, n, g]
  • word.split("") → gives String[]

  • map() gives Stream<String[]>

  • flatMap(Arrays::stream) flattens Stream<String[]>Stream<String>

  • distinct() removes duplicates

๐Ÿ”ฅ Trick: map(Arrays::stream) would give you Stream<Stream<String>> and fail to compile in .collect().



๐Ÿ”ฅ Q2: Why does this not compile?


List<List<Integer>> list = Arrays.asList( Arrays.asList(1, 2), Arrays.asList(3, 4) ); List<Stream<Integer>> result = list.stream() .map(l -> l.stream()) .collect(Collectors.toList());

Answer:

It compiles, but the result is List<Stream<Integer>>, not List<Integer>.

To flatten the list, use:


List<Integer> result = list.stream() .flatMap(List::stream) .collect(Collectors.toList());

๐Ÿ‘‰ Trick: flatMap is needed to flatten nested streams.


๐Ÿ”ฅ Q3: How is flatMap() different from map() in Optional?


Optional<String> name = Optional.of("Java"); Optional<Optional<String>> result = name.map(n -> Optional.of(n.toUpperCase())); Optional<String> flat = name.flatMap(n -> Optional.of(n.toUpperCase()));

Answer:

  • map() → wraps result → Optional<Optional<String>>

  • flatMap() → avoids wrapping → Optional<String>

๐Ÿง  Trick: Use flatMap when the function already returns an Optional.


๐Ÿ”ฅ Q4: What happens here?


Stream<String> stream = Stream.of("apple", "banana"); stream.map(s -> s.toUpperCase()); stream.forEach(System.out::println);

Answer:

๐Ÿ’ฅ Runtime Exception: IllegalStateException: stream has already been operated upon or closed

Why? Because map() doesn’t mutate the original stream — it returns a new one. But it was never assigned.

๐Ÿง  Trick: Streams are one-time use. Always assign or chain after map() or flatMap().


๐Ÿ”ฅ Q5: Can you convert a List<String> where each string is comma-separated into a List<String> of all elements?

Answer:

Yes, with flatMap():


List<String> input = Arrays.asList("a,b,c", "d,e"); List<String> result = input.stream() .map(s -> s.split(",")) .flatMap(Arrays::stream) .collect(Collectors.toList());

Output:


[a, b, c, d, e]

๐Ÿ’ก Trick: split() gives arrays. Need flatMap(Arrays::stream) to flatten.


๐Ÿ”ฅ Q6: What’s the output?


List<String> list = Arrays.asList("a,b", "c,d", "e"); List<String[]> mapped = list.stream() .map(s -> s.split(",")) .collect(Collectors.toList()); System.out.println(mapped.size()); // ? System.out.println(mapped.get(0)[0]); // ?

Answer:

  • mapped.size() → 3

  • mapped.get(0)[0]"a"

๐Ÿง  Trick: map() does not flatten — you still have a List<String[]>, not a flat List<String>. Only flatMap() can do that.


๐Ÿ”ฅ Q7: What does this do?


Stream<String> stream = Stream.of("java", "spring"); Stream<Stream<Character>> result = stream.map(str -> str.chars().mapToObj(c -> (char) c) );

Answer:

  • It creates a Stream<Stream<Character>>

  • Each inner stream represents characters of a string

✅ If you want a flat Stream<Character>:


Stream<Character> flat = stream .flatMap(str -> str.chars().mapToObj(c -> (char) c));

๐Ÿ”ฅ Trick: Interviewers love chars + streams + mapping → always pay attention to return types.


๐Ÿ”ฅ Q8: Can you use flatMap() with primitives?

Answer:

Yes, but carefully! Primitives like IntStream, LongStream etc. don't work directly with flatMap.

Example:


List<String> list = Arrays.asList("1,2", "3,4"); List<Integer> result = list.stream() .map(s -> s.split(",")) .flatMap(Arrays::stream) .map(Integer::parseInt) .collect(Collectors.toList());

๐Ÿ”ฅ Trick: To flatMap a primitive stream, use:


.flatMapToInt(str -> str.chars()) // returns IntStream

๐Ÿ”ฅ Q9: Which is more efficient: map().flatMap() vs flatMap() directly?

Answer:

  • If you're nesting transformations (e.g. map().map()), it's fine.

  • But if you're mapping and flattening, flatMap() is better.

Bad:


.stream() .map(x -> someMethodReturningStream(x)) .map(stream -> stream.collect(Collectors.toList())) // Oops!

Better:


.stream() .flatMap(this::someMethodReturningStream)

๐Ÿ”ฅ Trick: .flatMap() avoids intermediate structures (like List of Lists) — better for performance and memory.


๐Ÿ”ฅ Q10: What if flatMap returns null?


Stream.of("hello", "world") .flatMap(s -> null) // What happens here? .collect(Collectors.toList());

Answer:

๐Ÿ’ฅ NullPointerException

❌ You cannot return null from a flatMap — it must return a valid Stream. If you want an empty result, return Stream.empty().

Correct way:


.flatMap(s -> Stream.empty())

๐Ÿ”ฅ Trick: Always return a Stream, even if it’s empty. No null allowed in flatMap().


๐Ÿงช Final Trick:

"Use map() when you transform values. Use flatMap() when you're dealing with nested structures (like lists of lists or Optionals of Optionals)."