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%.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Cut Controller Code by 30% with Zero‑Intrusion Unified Responses

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: 1024

Annotation 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%.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpring BootREST APIjava-21response-wrappermicrometerspring-response-envelope
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.