juneau-bean-rfc7807
The juneau-bean-rfc7807 module provides Java beans for RFC 7807 — Problem Details for HTTP APIs, the canonical machine-readable error format used by application/problem+json.
Overview
RFC 7807 standardises a five-field JSON document for carrying HTTP error details. Most modern REST stacks ship a ProblemDetail-style bean out of the box (Spring's ProblemDetail, ASP.NET's ProblemDetails, etc.); this module brings the same to Juneau as a typed @Marshalled POJO that round-trips through any Juneau serializer or parser.
RFC 7807 was obsoleted by RFC 9457 in July 2023, but the data model and the application/problem+json IANA registration are unchanged. The module name keeps the historical RFC reference to match the existing ContentType.APPLICATION_PROBLEM_JSON constant lineage.
Key Features
- Single
Problembean — Covers the five canonical members in §3.1 plus open-ended extension members in §3.2. - Lazy
typedefault —getType()returns the raw nullable value;getTypeOrDefault()returns the spec default"about:blank"per §3.1 when the field is unset, without mutating the bean or polluting the wire output. - Extension members flatten — Arbitrary §3.2 extension members appear at the top level of the JSON via a
@BeanProp("*")triplet over a privateMap<String,Object>. - Fluent setters — Every setter returns
thisfor ergonomic chaining. - No external dependencies — Only depends on
juneau-marshall.
The Problem Bean
| Property | Type | Notes |
|---|---|---|
type | URI | Nullable. getType() returns raw value; getTypeOrDefault() returns about:blank. |
title | String | Short, human-readable summary of the problem type. |
status | Integer | OPTIONAL HTTP status code. Serialized as a JSON number; omitted when null. |
detail | String | Human-readable explanation specific to this occurrence. |
instance | URI | URI that identifies the specific occurrence of the problem. |
| (extras) | varies | Arbitrary §3.2 extension members; serialise flat at the top level. |
Static factory
A single convenience factory covers the common "build a problem from a status code + title + detail" call site:
Problem p = Problem.fromStatus(404, "Not Found", "Resource missing");
Callers bridge from BasicHttpException (or any other exception type) themselves in one line, keeping the bean module free of an outbound dep on juneau-rest-common:
Problem p = Problem.fromStatus(
e.getStatusCode(),
e.getStatusLine().getReasonPhrase(),
e.getMessage()
);
Basic Usage
Building a problem document
import org.apache.juneau.bean.rfc7807.*;
import org.apache.juneau.marshall.json.*;
Problem problem = new Problem()
.setType(new URI("https://example.com/probs/out-of-credit"))
.setTitle("You do not have enough credit.")
.setStatus(403)
.setDetail("Your current balance is 30, but that costs 50.")
.setInstance(new URI("/account/12345/msgs/abc"))
.set("balance", 30)
.set("accounts", List.of("/account/12345", "/account/67890"));
String wire = JsonSerializer.DEFAULT.write(problem);
Problem back = JsonParser.DEFAULT.read(wire, Problem.class);
Produces:
{
"detail": "Your current balance is 30, but that costs 50.",
"instance": "/account/12345/msgs/abc",
"status": 403,
"title": "You do not have enough credit.",
"type": "https://example.com/probs/out-of-credit",
"balance": 30,
"accounts": ["/account/12345", "/account/67890"]
}
The type default
RFC 7807 §3.1 says an absent type member is equivalent to the URI "about:blank". The Problem bean models this lazily: the field stays null when unset, so a fresh bean round-trips without a synthetic "type":"about:blank" on the wire. Callers who need the spec default applied use the separate getTypeOrDefault() accessor:
Problem p = new Problem();
p.getType(); // returns null
p.getTypeOrDefault(); // returns URI.create("about:blank")
JsonSerializer.DEFAULT.write(p); // "{}", no "type" member
Extension members
Arbitrary §3.2 extension members are added via set(String, Object) and read back via get(String) or extraKeys(). They are serialised flat into the top-level object — not nested under any extensions key.
Problem p = new Problem()
.setTitle("Validation failed")
.set("invalidParams", List.of("name", "age"));
// Wire: {"invalidParams":["name","age"],"title":"Validation failed"}
Problem parsed = JsonParser.DEFAULT.read(wire, Problem.class);
parsed.get("invalidParams"); // List<String> ["name", "age"]
parsed.extraKeys(); // Set<String> {"invalidParams"}
Content-Type
The matching Content-Type constants live in juneau-rest-common and have been available since 9.2.x:
import org.apache.juneau.http.header.ContentType;
ContentType.APPLICATION_PROBLEM_JSON; // "application/problem+json"
ContentType.APPLICATION_PROBLEM_XML; // "application/problem+xml"
This module supplies the application/problem+json bean; application/problem+xml is not yet covered.