Skip to main content

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 Problem bean — Covers the five canonical members in §3.1 plus open-ended extension members in §3.2.
  • Lazy type default — 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 private Map<String,Object>.
  • Fluent setters — Every setter returns this for ergonomic chaining.
  • No external dependencies — Only depends on juneau-marshall.

The Problem Bean​

PropertyTypeNotes
typeURINullable. getType() returns raw value; getTypeOrDefault() returns about:blank.
titleStringShort, human-readable summary of the problem type.
statusIntegerOPTIONAL HTTP status code. Serialized as a JSON number; omitted when null.
detailStringHuman-readable explanation specific to this occurrence.
instanceURIURI that identifies the specific occurrence of the problem.
(extras)variesArbitrary §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.

Resources​