Tuesday

Poor Exception Handling: 5 Mistakes Java Developers Keep Making

Poor Exception Handling: 5 Mistakes Java Developers Keep Making

Exception handling is one of the first things Java developers learn, yet it is also one of the easiest areas to get wrong.

Most developers focus heavily on the happy path: implementing features, writing business logic, and getting tests to pass. But production systems rarely fail because of the happy path. They fail because unexpected situations weren't handled properly.

After reviewing countless Java codebases over the years, I've noticed the same exception handling mistakes appearing again and again. These mistakes make applications harder to debug, harder to maintain, and more likely to cause production incidents.

Let's look at five common mistakes and how experienced Java developers handle them.


1. Catching Generic Exceptions

One of the most common anti-patterns is catching the generic Exception class.

❌ Bad

try {
    processOrder();
} catch (Exception e) {
    logger.error("Something went wrong");
}

At first glance, this might seem convenient. Unfortunately, it creates several problems:

  • It hides the real issue.
  • It makes troubleshooting more difficult.
  • It catches exceptions you may not have intended to handle.
  • It reduces the clarity of your code.

✅ Better

try {
    processOrder();
} catch (OrderValidationException e) {
    logger.warn("Invalid order", e);
}

By catching specific exceptions, your code communicates intent more clearly and makes failures easier to diagnose.

Senior Takeaway: Catch the most specific exception possible.

2. Swallowing Exceptions

Another dangerous practice is catching an exception and doing absolutely nothing with it.

❌ Bad

try {
    sendEmail();
} catch (MessagingException e) {
    // ignore
}

This is often referred to as "swallowing" an exception.

When this happens:

  • Failures disappear silently.
  • Production issues become difficult to identify.
  • Monitoring systems have no visibility into problems.
  • Users may experience failures with no explanation.

✅ Better

try {
    sendEmail();
} catch (MessagingException e) {
    logger.error("Failed to send email", e);
    throw new NotificationException(
        "Unable to send email", e
    );
}

This version logs the failure and propagates meaningful context to the caller.

Senior Takeaway: Every exception should be handled, logged, or propagated.

3. Losing the Original Stack Trace

Many developers wrap exceptions without preserving the original cause.

❌ Bad

try {
    repository.save(user);
} catch (SQLException e) {
    throw new RuntimeException("Database error");
}

The issue is subtle but significant.

The original SQLException is discarded, along with the valuable stack trace that explains what actually went wrong.

✅ Better

try {
    repository.save(user);
} catch (SQLException e) {
    throw new RuntimeException(
        "Database error", e
    );
}

Now the original exception is preserved as the cause.

When someone investigates a production issue months later, they'll thank you.

Senior Takeaway: Always preserve the root cause.

4. Using Exceptions for Normal Control Flow

Exceptions are meant for exceptional situations, not routine business logic.

❌ Bad

try {
    Integer.parseInt(input);
} catch (NumberFormatException e) {
    return false;
}

While this works, it uses an exception to control expected behavior.

Problems include:

  • Reduced readability.
  • Unnecessary object creation.
  • Performance overhead.
  • Business logic becoming harder to understand.

✅ Better

return input.matches("-?\\d+");

In many situations, validating data before processing it is a cleaner approach. If you also need the parsed value, prefer a method that returns a result instead of throwing, so the exception no longer drives normal control flow.

Senior Takeaway: Exceptions should represent exceptional situations.

5. Exposing Internal Errors to API Consumers

Many applications accidentally expose technical details directly to users or API consumers.

❌ Bad

@GetMapping("/users/{id}")
public User getUser(Long id) {
    return service.findUser(id);
}

This can lead to responses like:

{
  "exception": "NullPointerException",
  "stackTrace": "..."
}

This creates several issues:

  • Security risks
  • Information leakage
  • Poor user experience
  • Unprofessional APIs

Users don't care about your stack traces. They care about understanding what went wrong.

✅ Better

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handle() {

        return ResponseEntity.status(404)
            .body(new ErrorResponse("User not found"));
    }
}

This approach provides a clean, meaningful response while hiding implementation details.

Senior Takeaway: Return business-friendly errors, not technical internals.

What Senior Java Developers Do Differently

As developers gain experience, their approach to exception handling evolves.

  • ✅ Use custom exceptions where appropriate
  • ✅ Preserve root causes when wrapping exceptions
  • ✅ Log errors at the correct severity level
  • ✅ Centralize exception handling
  • ✅ Return clean API responses
  • ✅ Avoid empty catch blocks
  • ✅ Fail fast when necessary
  • ✅ Treat error handling as part of application design, not an afterthought

Final Thoughts

Good exception handling doesn't just prevent crashes.

It makes software easier to debug, easier to maintain, and easier to operate in production.

The difference between junior and senior Java code is often not visible in the happy path. Most code looks good when everything works. The real difference appears when things go wrong.

The next time you review your code, pay special attention to how failures are handled. You may discover that improving your exception handling has a bigger impact on code quality than adding another feature.

Remember: Great developers don't just write code that works, they write code that fails gracefully.

Monday

Handling Trailing Slashes in Spring 6: Introducing UrlHandlerFilter

Handling Trailing Slashes in Spring 6: Introducing UrlHandlerFilter

Overview

When migrating applications to Spring Framework 6 / Spring Boot 3, many teams encounter unexpected 404 errors due to URL pattern changes—especially around trailing slash handling.

In previous versions of Spring, URLs with or without a trailing slash were treated the same:

/api/users
/api/users/

However, Spring 6 no longer supports this behavior by default, requiring explicit handling.


The Problem After Migration

After upgrading:

  • /api/resource ✅ works
  • /api/resource/ ❌ may return 404

Example:

@GetMapping("/users")
public List<User> getUsers() { ... }

Calling GET /users/ will fail unless handled explicitly.


Common Fixes (and Limitations)

1. Duplicate mappings

@GetMapping({"/users", "/users/"})

✅ Works
❌ Not scalable

2. Enable trailing slash globally

configurer.setUseTrailingSlashMatch(true);

❌ Deprecated in Spring 6


✅ Introducing UrlHandlerFilter

Spring 6 provides a better solution: UrlHandlerFilter.

This filter helps normalize incoming URLs by:

  • Removing trailing slashes
  • Redirecting URLs
  • Standardizing request paths

Example: Remove Trailing Slash

Configuration

@Configuration
public class FilterConfig {

    @Bean
    public UrlHandlerFilter trailingSlashFilter() {
        return UrlHandlerFilter
                // Use .trailingSlashHandler("/**") in newer 6.2+ versions
                .trimTrailingSlash("/**") 
                .andHandleRequest() // Wraps request internally
                .build();
    }
}

Result

  • /users//users
  • /orders/123//orders/123

✔ No controller changes needed
✔ Centralized solution


Optional: Redirect Instead

UrlHandlerFilter.redirectTrailingSlash("/**");

This will return a 301 redirect:

  • /users//users

When to Use It

  • Migrating legacy systems
  • Cannot update all clients immediately
  • Want centralized handling

Best Practices

  • Short-term: Use trimTrailingSlash
  • Long-term: Standardize API without trailing slash

Summary

  • Duplicate mapping → ❌ Not recommended
  • Global trailing slash → ❌ Deprecated
  • UrlHandlerFilter → ✅ Best approach

Final Thoughts

Spring 6 introduces stricter URL handling, which improves API clarity but requires careful migration.

UrlHandlerFilter is a clean and scalable solution to handle trailing slashes without modifying every controller.

Tuesday

RESTController vs Controller in Spring Applications: What’s the Difference?

RestController vs Controller in Spring Applications

RestController vs Controller in Spring Applications: What’s the Difference?

When building web applications with Spring, developers often wonder: Should I use @Controller or @RestController? Both annotations define web components, but they serve different purposes. Understanding the distinction is crucial for designing clean, maintainable applications.

1. What is @Controller?

@Controller is part of Spring MVC and is primarily used for server-side rendering. It works with view technologies like Thymeleaf, JSP, or FreeMarker.

  • Purpose: Return views (HTML pages) to the client.
  • Behavior: Methods typically return a String representing the view name.
  • Data Handling: Use Model or ModelAndView to pass data to the view.

Example:


@Controller
public class PageController {

    @GetMapping("/home")
    public String home(Model model) {
        model.addAttribute("message", "Welcome!");
        return "home"; // Resolved by ViewResolver
    }

    @GetMapping("/status")
    @ResponseBody
    public Map<String, String> status() {
        return Map.of("status", "ok");
    }
}
    

2. What is @RestController?

@RestController is designed for RESTful APIs. It combines @Controller and @ResponseBody, meaning every method returns data directly (usually JSON) instead of a view.

  • Purpose: Build REST APIs for SPAs, mobile apps, or microservices.
  • Behavior: Methods return objects, which Spring serializes using HttpMessageConverters (e.g., Jackson for JSON).

Example:


@RestController
@RequestMapping("/api")
public class UserApi {

    @GetMapping("/users/{id}")
    public UserDto getUser(@PathVariable Long id) {
        return new UserDto(id, "Alice");
    }
}
    

3. Key Differences

Feature @Controller @RestController
Default Behavior Returns view name Returns JSON/XML
View Rendering ✅ Yes ❌ No
Needs @ResponseBody Yes (for JSON) No (implicit)
Typical Use Case MVC web pages REST APIs

4. When to Use Which?

  • Use @Controller if you’re building traditional web pages with server-side rendering.
  • Use @RestController if you’re exposing REST endpoints for front-end apps or other services.

5. Common Pitfalls

  • Returning a view name from @RestController will send "home" as JSON, not render a page.
  • Forgetting @ResponseBody in @Controller when returning JSON will cause view resolution errors.

Conclusion

Both annotations are powerful, but they serve different roles. For modern applications with APIs, @RestController is the go-to choice. For classic MVC apps, stick with @Controller.

Wednesday

Linux: Free memory

I simply wish to share a post regarding Linux that aims to clarify the distinction between free memory and available memory.
You may interpret that memory as "free" or "available". However, Linux categorizes it solely as "available".

The detailed article can be found at this URL: https://www.linuxatemyram.com/

Sunday

Fast English Word Learning with Flashcard Generator

Introducing a tool that generates flashcards for preschoolers learning English. With just the words input, this tool creates visually appealing flashcards with buttons to hear the word and search related images using Bing. It's the perfect way to accelerate language learning for young children.



Benefits:

- Expand vocabulary quickly

- Engage multiple senses for effective learning

- Interactive and fun experience

Try it: Flashcard Generator