Skip to content

Commit 1525f94

Browse files
Add tutorial page
1 parent c0de615 commit 1525f94

1 file changed

Lines changed: 99 additions & 0 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
---
2+
sidebar_position: 3
3+
---
4+
5+
# Migration from Zalando Problem
6+
7+
> Motivation for this article is the fact that [Zalando's `problem-spring-web`][zalando-problem-spring-web] did not
8+
> upgrade to Spring Boot 4 and has moved to **maintenance mode**.
9+
10+
[Zalando's `problem`][zalando-problem] library was one of the first popular implementations of [RFC 7807][rfc7807], and
11+
many existing Spring applications still throw `AbstractThrowableProblem` subclasses to signal errors. Problem4J follows
12+
the same RFC, but with a different philosophy: instead of exceptions carrying the response shape themselves,
13+
a `Problem` is a plain, immutable value object, decoupled from `Throwable`, and exceptions are converted to it via
14+
`ProblemResolver`, `@ProblemMapping`, or `ProblemException`. This makes error handling easier to test, easier to reuse
15+
across non-Spring modules, and avoids mixing control-flow (exceptions) with response payloads (data).
16+
17+
Rewriting every exception in a large codebase in one go usually isn't realistic, so this guide shows how to run both
18+
libraries side by side: keep your existing `AbstractThrowableProblem` exceptions working exactly as before, while new
19+
code is written against Problem4J. Once the old exceptions are migrated (or simply left to throw plain exceptions
20+
mapped through `@ProblemMapping`/`ProblemResolver`), the `zalando:problem` dependency and the bridge below can be
21+
removed entirely.
22+
23+
## Getting Started
24+
25+
```xml
26+
<dependencies>
27+
<dependency>
28+
<groupId>io.github.problem4j</groupId>
29+
<artifactId>problem4j-spring-webmvc</artifactId>
30+
<version>3.0.0</version>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.zalando</groupId>
34+
<artifactId>problem</artifactId>
35+
<version>1.0.0</version>
36+
</dependency>
37+
</dependencies>
38+
```
39+
40+
```kt
41+
dependencies {
42+
implementation("io.github.problem4j:problem4j-spring-webmvc:3.0.0")
43+
implementation("org.zalando:problem:1.0.0")
44+
}
45+
```
46+
47+
## Bridging Zalando Problem to Problem4J
48+
49+
Register a `ProblemResolver` for `AbstractThrowableProblem`, the common base class of every Zalando problem. Since
50+
Problem4J's `@RestControllerAdvice`-s dispatch by exact exception class, catching the abstract base class means
51+
**all** of your existing Zalando problem exceptions are handled in one place, with no per-exception code required.
52+
The resolver simply copies each field over to Problem4J's `Problem.builder()`, so the response body stays identical
53+
to what Zalando's library would have produced.
54+
55+
```java
56+
package org.example;
57+
58+
import io.github.problem4j.core.Problem;
59+
import io.github.problem4j.core.ProblemContext;
60+
import io.github.problem4j.spring.web.resolver.ProblemResolver;
61+
import org.springframework.http.HttpHeaders;
62+
import org.springframework.http.HttpStatusCode;
63+
import org.springframework.stereotype.Component;
64+
import org.zalando.problem.AbstractThrowableProblem;
65+
66+
@Component
67+
public class ThrowableProblemResolver implements ProblemResolver {
68+
69+
@Override
70+
public Class<? extends Exception> getExceptionClass() {
71+
return AbstractThrowableProblem.class;
72+
}
73+
74+
@Override
75+
public Problem resolve(ProblemContext context, Exception ex, HttpHeaders headers, HttpStatusCode status) {
76+
AbstractThrowableProblem e = (AbstractThrowableProblem) ex;
77+
return Problem.builder()
78+
.type(e.getType())
79+
.title(e.getTitle())
80+
.status(e.getStatus() != null ? e.getStatus().getStatusCode() : 0)
81+
.detail(e.getDetail())
82+
.instance(e.getInstance())
83+
.extensions(e.getParameters())
84+
.build();
85+
}
86+
}
87+
```
88+
89+
With this resolver in place, existing code that throws `AbstractThrowableProblem` subclasses keeps working unchanged,
90+
while any new exception can be written against Problem4J directly using `ProblemException`, `@ProblemMapping`, or its
91+
own `ProblemResolver`, as described in [Exception Handling](../problem4j-spring/exception-handling). Once every
92+
`AbstractThrowableProblem` usage has been migrated, delete this resolver together with the `org.zalando:problem`
93+
dependency.
94+
95+
[zalando-problem]: https://github.com/zalando/problem
96+
97+
[zalando-problem-spring-web]: https://github.com/zalando/problem-spring-web
98+
99+
[rfc7807]: https://datatracker.ietf.org/doc/html/rfc7807

0 commit comments

Comments
 (0)