Fork me on GitHub

Exception Handling in OfficeFloor REST YAML Composition

This tutorial demonstrates how exceptions (called escalations in OfficeFloor) are handled within the YAML REST composition when integrating with Spring Boot.

Three levels of exception handling are covered:

  1. Method escalation - the exception is caught and routed to a handler declared on the specific method in the YAML.
  2. Composition escalation - the exception is caught and routed to a handler declared at the composition level, applying across all methods.
  3. Spring ControllerAdvice - the exception propagates out of the composition and is handled by Spring's @RestControllerAdvice / @ExceptionHandler mechanism.

    Tutorial Source

The Exception

All three scenarios throw the same checked exception. Declaring it as a checked exception (extends Exception) ensures the throws clause appears in the method signature, which is required for OfficeFloor to discover and route the escalation through the YAML configuration.

public class MockException extends Exception {

    public MockException(String message) {
        super(message);
    }
}

Method Escalation

Method escalation routes an exception thrown by a specific method to a handler step declared within the same YAML composition.

YAML configuration

The escalations block sits under the step that throws the exception. Each entry maps the fully-qualified exception class name to the name of the handler step to invoke.

service:
  class: net.officefloor.spring.starter.rest.exception.MethodService
  escalations:
    net.officefloor.spring.starter.rest.exception.MockException: handler

handler:
  class: net.officefloor.spring.starter.rest.exception.MethodExceptionHandler

When service throws MockException, OfficeFloor routes execution to the handler step rather than letting the exception propagate further.

Service method

The service method simply declares and throws the exception:

public class MethodService {
    public void service() throws MockException {
        throw new MockException("thrown");
    }
}

Handler method

The handler receives the thrown exception via the @Parameter annotation, which binds the escalation object as a method parameter. From there it can inspect the exception and write a response using ObjectResponse:

public class MethodExceptionHandler {
    public void handle(@Parameter MockException ex, ObjectResponse<String> response) {
        response.send("Method handled: " + ex.getMessage());
    }
}

The @Parameter annotation is provided by OfficeFloor and signals that the parameter value is passed from the previous step's output — in the escalation case this is the thrown exception.

Composition Escalation

Composition escalation moves the escalation routing up to the composition level. Rather than declaring it on a single method, it applies to every method in the composition. This is useful when multiple service steps share a common failure mode that should be handled in one place.

YAML configuration

The composition block at the top of the YAML file holds the shared escalation mappings:

composition:
  escalations:
    net.officefloor.spring.starter.rest.exception.MockException: handler

service:
  class: net.officefloor.spring.starter.rest.exception.CompositionService

handler:
  class: net.officefloor.spring.starter.rest.exception.CompositionExceptionHandler

Any step in the composition that throws MockException — whether it is service or any other step added later — is automatically routed to the handler step.

Service and handler methods

The service class is identical in structure to the method-escalation example:

public class CompositionService {
    public void service() throws MockException {
        throw new MockException("thrown");
    }
}

The handler class is also the same pattern, just producing a different response to illustrate which level caught the exception:

public class CompositionExceptionHandler {
    public void handle(@Parameter MockException ex, ObjectResponse<String> response) {
        response.send("Composition handled: " + ex.getMessage());
    }
}

The key difference from method escalation is entirely in the YAML — the Java classes are written the same way regardless of which level handles the escalation.

Spring ControllerAdvice

When neither a method nor a composition escalation is configured, the exception propagates out of the OfficeFloor composition and is handled by Spring's standard exception handling infrastructure. This allows OfficeFloor REST endpoints to participate in an application's existing @RestControllerAdvice setup without any extra configuration.

YAML configuration

The YAML for this endpoint needs no escalation configuration at all:

service:
  class: net.officefloor.spring.starter.rest.exception.SpringAdviceService

Service method

The service method throws the exception in the same way as before:

public class SpringAdviceService {
    public void service() throws MockException {
        throw new MockException("thrown");
    }
}

ExceptionControllerAdvice

The @RestControllerAdvice class provides the @ExceptionHandler method. Spring resolves the HTTP response status via @ResponseStatus:

@RestControllerAdvice
public class ExceptionControllerAdvice {

    @ExceptionHandler(MockException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handleMockException(MockException ex) {
        return "Spring handled: " + ex.getMessage();
    }
}

Because this is a standard Spring mechanism, all the usual Spring features apply — returning ResponseEntity, reading the HttpServletRequest, accessing Spring beans, and so on.

Choosing an approach

Approach When to use
Method escalation Handle an exception only for a specific step in the composition — for example a step with its own distinct recovery path.
Composition escalation Handle an exception the same way for every step in the composition — common for infrastructure errors such as database timeouts.
Spring ControllerAdvice Re-use existing application-wide Spring exception handling, or integrate with Spring libraries that register their own advice.

Unit Tests

The tests use Spring Boot's MockMvc to call each endpoint and assert the response:

package net.officefloor.spring.starter.rest.exception;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
public class ExceptionHandlingTutorialTest {

    @Autowired
    protected MockMvc mvc;

    @Test
    public void method_exception_handling() throws Exception {
        this.mvc.perform(get("/exception/method"))
                .andExpect(status().isOk())
                .andExpect(content().string("Method handled: thrown"));
    }

    @Test
    public void composition_exception_handling() throws Exception {
        this.mvc.perform(get("/exception/composition"))
                .andExpect(status().isOk())
                .andExpect(content().string("Composition handled: thrown"));
    }

    @Test
    public void spring_controller_advice() throws Exception {
        this.mvc.perform(get("/exception/spring"))
                .andExpect(status().isBadRequest())
                .andExpect(content().string("Spring handled: thrown"));
    }
}

Next

The Spring REST Security tutorial demonstrates Spring Security integration with OfficeFloor REST YAML composition.