Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

November Discount Launch 2022 – Top
We’re finally running a Black Friday launch. All Courses are 30% off until tomorrow:

>> GET ACCESS NOW

NPI – Lightrun – Spring (partner)

We rely on other people’s code in our own work. Every day. It might be the language you’re writing in, the framework you’re building on, or some esoteric piece of software that does one thing so well you never found the need to implement it yourself.

The problem is, of course, when things fall apart in production - debugging the implementation of a 3rd party library you have no intimate knowledge of is, to say the least, tricky. It’s difficult to understand what talks to what and, specifically, which part of the underlying library is at fault.

Lightrun is a new kind of debugger.

It's one geared specifically towards real-life production environments. Using Lightrun, you can drill down into running applications, including 3rd party dependencies, with real-time logs, snapshots, and metrics. No hotfixes, redeployments, or restarts required.

Learn more in this quick, 5-minute Lightrun tutorial:

>> The Essential List of Spring Boot Annotations and Their Use Cases

1. Overview

In this tutorial, we're going to explore how to produce application/problem+json responses using the Problem Spring Web library. This library helps us to avoid repetitive tasks related to error handling.

By integrating Problem Spring Web into our Spring Boot application, we can simplify the way we handle exceptions within our project and generate responses accordingly.

2. The Problem Library

Problem is a small library with the purpose of standardizing the way Java-based Rest APIs express errors to their consumers.

A Problem is an abstraction of any error we want to inform about. It contains handy information about the error. Let's see the default representation of a Problem response:

{
  "title": "Not Found",
  "status": 404
}

In this case, the status code and the title are enough to describe the error. However, we can also add a detailed description of it:

{
  "title": "Service Unavailable",
  "status": 503,
  "detail": "Database not reachable"
}

We can also create custom Problem objects that adapt to our needs:

Problem.builder()
  .withType(URI.create("https://example.org/out-of-stock"))
  .withTitle("Out of Stock")
  .withStatus(BAD_REQUEST)
  .withDetail("Item B00027Y5QG is no longer available")
  .with("product", "B00027Y5QG")
  .build();

In this tutorial we'll focus on the Problem library implementation for Spring Boot projects.

3. Problem Spring Web Setup

Since this is a Maven based project, let's add the problem-spring-web dependency to the pom.xml:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>problem-spring-web</artifactId>
    <version>0.23.0</version>
</dependency>
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.4.0</version> 
</dependency>
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.4.0</version>  
</dependency>

We also need the spring-boot-starter-web and the spring-boot-starter-security dependencies. Spring Security is required from version 0.23.0 of problem-spring-web.

4. Basic Configuration

As our first step, we need to disable the white label error page so we'll able to see our custom error representation instead:

@EnableAutoConfiguration(exclude = ErrorMvcAutoConfiguration.class)

Now, let's register some of the required components in the ObjectMapper bean:

@Bean
public ObjectMapper objectMapper() {
    return new ObjectMapper().registerModules(
      new ProblemModule(),
      new ConstraintViolationProblemModule());
}

After that, we need to add the following properties to the application.properties file:

spring.resources.add-mappings=false
spring.mvc.throw-exception-if-no-handler-found=true
spring.http.encoding.force=true

And finally, we need to implement the ProblemHandling interface:

@ControllerAdvice
public class ExceptionHandler implements ProblemHandling {}

5. Advanced Configuration

In addition to the basic configuration, we can also configure our project to handle security-related problems. The first step is to create a configuration class to enable the library integration with Spring Security:

@Configuration
@EnableWebSecurity
@Import(SecurityProblemSupport.class)
public class SecurityConfiguration {

    @Autowired
    private SecurityProblemSupport problemSupport;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // Other security-related configuration
        http.exceptionHandling()
          .authenticationEntryPoint(problemSupport)
          .accessDeniedHandler(problemSupport);
        return http.build();
    }
}

And finally, we need to create an exception handler for security-related exceptions:

@ControllerAdvice
public class SecurityExceptionHandler implements SecurityAdviceTrait {}

6. The REST Controller

After configuring our application, we are ready to create a RESTful controller:

@RestController
@RequestMapping("/tasks")
public class ProblemDemoController {

    private static final Map<Long, Task> MY_TASKS;

    static {
        MY_TASKS = new HashMap<>();
        MY_TASKS.put(1L, new Task(1L, "My first task"));
        MY_TASKS.put(2L, new Task(2L, "My second task"));
    }

    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    public List<Task> getTasks() {
        return new ArrayList<>(MY_TASKS.values());
    }

    @GetMapping(value = "/{id}",
      produces = MediaType.APPLICATION_JSON_VALUE)
    public Task getTasks(@PathVariable("id") Long taskId) {
        if (MY_TASKS.containsKey(taskId)) {
            return MY_TASKS.get(taskId);
        } else {
            throw new TaskNotFoundProblem(taskId);
        }
    }

    @PutMapping("/{id}")
    public void updateTask(@PathVariable("id") Long id) {
        throw new UnsupportedOperationException();
    }

    @DeleteMapping("/{id}")
    public void deleteTask(@PathVariable("id") Long id) {
        throw new AccessDeniedException("You can't delete this task");
    }

}

In this controller, we're intentionally throwing some exceptions. Those exceptions will be converted into Problem objects automatically to produce an application/problem+json response with the details of the failure.

Now, let's talk about the built-in advice traits and also how to create a custom Problem implementation.

7. Built-in Advice Traits

An advice trait is a small exception handler that catches exceptions and returns the proper problem object.

There are built-in advice traits for common exceptions. Hence, we can use them by simply throwing the exception:

throw new UnsupportedOperationException();

As a result, we'll get the response:

{
    "title": "Not Implemented",
    "status": 501
}

Since we configured the integration with Spring Security as well, we're able to throw security-related exceptions:

throw new AccessDeniedException("You can't delete this task");

And get the proper response:

{
    "title": "Forbidden",
    "status": 403,
    "detail": "You can't delete this task"
}

8. Creating a Custom Problem

It's possible to create a custom implementation of a Problem. We just need to extend the AbstractThrowableProblem class:

public class TaskNotFoundProblem extends AbstractThrowableProblem {

    private static final URI TYPE
      = URI.create("https://example.org/not-found");

    public TaskNotFoundProblem(Long taskId) {
        super(
          TYPE,
          "Not found",
          Status.NOT_FOUND,
          String.format("Task '%s' not found", taskId));
    }

}

And we can throw our custom problem as follows:

if (MY_TASKS.containsKey(taskId)) {
    return MY_TASKS.get(taskId);
} else {
    throw new TaskNotFoundProblem(taskId);
}

As a result of throwing the TaskNotFoundProblem problem, we'll get:

{
    "type": "https://example.org/not-found",
    "title": "Not found",
    "status": 404,
    "detail": "Task '3' not found"
}

9. Dealing with Stack Traces

If we want to include stack traces within the response, we need to configure our ProblemModule accordingly:

ObjectMapper mapper = new ObjectMapper()
  .registerModule(new ProblemModule().withStackTraces());

The causal chain of causes is disabled by default, but we can easily enable it by overriding the behavior:

@ControllerAdvice
class ExceptionHandling implements ProblemHandling {

    @Override
    public boolean isCausalChainsEnabled() {
        return true;
    }

}

After enabling both features we'll get a response similar to this one:

{
  "title": "Internal Server Error",
  "status": 500,
  "detail": "Illegal State",
  "stacktrace": [
    "org.example.ExampleRestController
      .newIllegalState(ExampleRestController.java:96)",
    "org.example.ExampleRestController
      .nestedThrowable(ExampleRestController.java:91)"
  ],
  "cause": {
    "title": "Internal Server Error",
    "status": 500,
    "detail": "Illegal Argument",
    "stacktrace": [
      "org.example.ExampleRestController
        .newIllegalArgument(ExampleRestController.java:100)",
      "org.example.ExampleRestController
        .nestedThrowable(ExampleRestController.java:88)"
    ],
    "cause": {
      // ....
    }
  }
}

10. Conclusion

In this article, we explored how to use the Problem Spring Web library to create responses with the errors' details using an application/problem+json response. We also learned how to configure the library in our Spring Boot application and create a custom implementation of a Problem object.

The implementation of this guide can be found in the GitHub project – this is a Maven based project, so it should be easy to import and run it as is.

November Discount Launch 2022 – Bottom
We’re finally running a Black Friday launch. All Courses are 30% off until tomorrow:

>> GET ACCESS NOW

Generic footer banner
Comments are closed on this article!