Cut Controller Code by 30% with Zero‑Intrusion Unified Responses
This article introduces the open‑source spring‑response‑envelope library for Spring Boot 3, showing how a single @ResponseEnvelope annotation can automatically wrap controller results in a standard JSON structure, add metadata, handle errors, expose metrics, and reduce boilerplate code by about 30%.
In a front‑back separated architecture, a unified RESTful response format (containing fields such as code, msg, data) is a de‑facto standard, yet developers often have to litter their controller methods with repetitive calls like Result.success(data), which inflates code size and hampers productivity.
Zero‑intrusion solution
The article presents spring‑response‑envelope , an open‑source component built for Spring Boot 3.5.x that intercepts the response body and automatically wraps it according to a company‑grade schema without requiring any changes to business logic. Developers simply add the @ResponseEnvelope annotation and the library handles the rest.
Key features
🟢 Single annotation – add @ResponseEnvelope to any handler.
📦 Unified format – all endpoints return a consistent JSON structure.
🔍 Rich metadata – timestamp, request ID, path, HTTP method, duration, API version, etc.
🚨 Centralised error handling – detailed error payloads.
📊 Pagination support – automatic pagination metadata for Page / Slice results.
🔗 Optional HATEOAS links.
📈 Micrometer integration for monitoring.
⚙️ Highly configurable via application.yml.
🔄 Built‑in API versioning.
Maven dependency
<dependency>
<groupId>io.github.overrridee</groupId>
<artifactId>spring-response-envelope</artifactId>
<version>0.2.1</version>
</dependency>Environment requirements
Java 21+
Spring Boot 3.5.x+
Basic usage
Annotate a controller method with @ResponseEnvelope and return the raw domain object:
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
@ResponseEnvelope
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}The library automatically produces the following JSON:
{
"success": true,
"data": {"id":42,"name":"Pack_xg","email":"[email protected]"},
"timestamp":"2026-05-23 14:30:00",
"requestId":"xxxooo",
"path":"/api/users/42",
"method":"GET",
"duration":145,
"apiVersion":"v1"
}Core configuration (application.yml)
response-envelope:
enabled: true
default-config:
include-timestamp: true
include-request-id: true
include-path: true
include-method: true
include-duration: true
include-api-version: true
timestamp-format: ISO_8601
custom-timestamp-pattern: "yyyy-MM-dd HH:mm:ss"
request-id-header: X-Request-ID
propagate-request-id: true
default-version: v1
include-nulls: false
data-key: data
timezone: UTC
error-config:
include-stacktrace: false
include-exception-class: true
show-detailed-messages: true
include-field-errors: true
documentation-url-pattern: "http://www.pack.com/errors/{code}"
error-source: my-service
metrics:
enabled: true
slo-duration: 500
prefix: envelope_response
cache:
enabled: false
default-duration: 60
compression:
enabled: false
min-size: 1024Annotation attributes
@ResponseEnvelope(
version = "v2",
includeTimestamp = true,
timestampFormat = TimestampFormat.ISO_8601,
includeRequestId = true,
includePath = true,
includeMethod = true,
includeDuration = true,
includeApiVersion = true,
httpStatus = 200,
successMessage = "操作成功",
dataKey = "data",
group = "default",
includeNulls = false,
legacyMode = false,
wrapStrategy = WrapStrategy.ALWAYS,
customHeaders = {"X-Custom:value"},
customMetadata = {"key:value"},
cacheDuration = 0,
includePagination = true,
includeLinks = false,
compress = false,
debugMode = false,
deprecationWarning = "",
includeRateLimitInfo = false
)Class‑level annotation
@RestController
@RequestMapping("/api/users")
@ResponseEnvelope(version = "v2")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@GetMapping("/export")
@IgnoreEnvelope(reason = "二进制响应")
public byte[] exportUsers() {
return userService.exportToCsv();
}
}Error handling
Built‑in exceptions such as EntityNotFoundException, ValidationException, and BusinessException are automatically translated into a detailed error payload:
{
"success": false,
"message": "未找到ID为999的用户",
"timestamp": "2026-05-23 14:30:00",
"requestId": "xxxooo",
"path": "/api/users/999",
"method": "GET",
"duration": 45,
"apiVersion": "v1",
"errors": {
"code": "ERR_BIZ_001",
"message": "用户不存在",
"details": "请求的标识符为'999'的用户在系统中不存在",
"exception": "com.pack.exception.EntityNotFoundException",
"fieldErrors": [],
"documentationUrl": "http://www.pack.com/errors/ERR_BIZ_001"
}
}Custom fields
@GetMapping("/{id}")
@ResponseEnvelope
@EnvelopeField(name = "correlationId", value = "#{correlationId}")
@EnvelopeField(name = "region", value = "xj")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}Request‑ID propagation
If the incoming request lacks an ID, one is generated automatically.
If the X-Request-ID header is present, its value is used.
The ID is added to MDC for logging.
The ID is also added to the response headers.
Metrics
Enable metrics in the configuration as shown above. The library exposes the following Micrometer meters:
envelope_response_duration_seconds – response‑time histogram.
envelope_response_success_total – count of successful responses.
envelope_response_error_total – count of error responses.
envelope_response_slo_violation_total – count of SLO violations.
Best practices
✅ Do
Apply the annotation to all API endpoints for consistency.
Log the request ID in every log statement for traceability.
Document the response schema with OpenAPI annotations.
Validate the envelope format in integration tests.
❌ Do not
Use the envelope for binary file downloads – use @IgnoreEnvelope instead.
Apply it to streaming endpoints such as SSE or WebFlux.
Expect the envelope format for external webhooks that require a custom payload.
Apply it to GraphQL endpoints, which already provide structured responses.
Conclusion
By adopting spring-response-envelope, developers can keep controller methods focused on business logic, achieve a uniform response contract, gain built‑in error handling and observability, and reduce repetitive boilerplate by roughly 30%.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
