Skip to content

Conversation

@0x1306e6d
Copy link
Contributor

Motivation:

Following #6269, error handler decorators like CorsServerErrorHandler and ContentPreviewServerErrorHandler were introduced to support decorating ServerErrorHandler. However, these decorators only worked with ServerErrorHandlers and did not support ServiceErrorHandler configured via ServiceBindingBuilder.errorHandler(). If a ServiceErrorHandler is configured, the ServerErrorHandler acts as a fallback, so the error response is returned directly without passing through decorators:

final ServiceErrorHandler serviceErrorHandler = serverErrorHandler.asServiceErrorHandler();
final ServiceErrorHandler defaultErrorHandler =
errorHandler != null ? errorHandler.orElse(serviceErrorHandler) : serviceErrorHandler;

Modifications:

  • Extended DecoratingErrorHandlerFunction to support both ServerErrorHandler and ServiceErrorHandler.
  • Updated ServiceConfigBuilder to automatically decorate ServiceErrorHandler instances.

Result:

  • Error response content preview and CORS headers are now available for services configured with ServiceErrorHandler.

@coderabbitai
Copy link

coderabbitai bot commented Dec 30, 2025

📝 Walkthrough

Walkthrough

The pull request refactors the error handling decorator framework to support both ServerErrorHandler and ServiceErrorHandler types uniformly. Classes and interfaces are renamed for broader applicability, new method overloads are introduced, and decorator application is extended to the service-level error handling path.

Changes

Cohort / File(s) Summary
Decorator Framework
DecoratingErrorHandlerFunction.java, ErrorHandlerDecorators.java
Interface renamed from DecoratingServerErrorHandlerFunction to DecoratingErrorHandlerFunction with new overload for ServiceErrorHandler. ErrorHandlerDecorators class restructured with updated decorator list types, new static decorate(ServiceErrorHandler) method, and new inner class DecoratingServiceErrorHandler to mirror server-error decoration.
Error Handler Implementations
ContentPreviewErrorHandler.java, CorsErrorHandler.java
Both classes renamed (from ContentPreview/CorsServerErrorHandler) and now implement DecoratingErrorHandlerFunction. New public overloads added for ServiceErrorHandler delegate parameter, with extracted helper methods (maybeSetUpResponseContentPreviewer, maybeSetCorsHeaders) to centralize setup logic.
Integration Points
ServerBuilder.java, ServiceConfigBuilder.java, DefaultServerConfig.java
Call sites updated to use ErrorHandlerDecorators instead of ServerErrorHandlerDecorators; ServiceConfigBuilder and DefaultServerConfig now wrap error handlers with ErrorHandlerDecorators.decorate() for consistent decoration across server and service levels.

Poem

🐰 Our errors now dance in decorator's refrain,
Where Service and Server both walk the same lane,
With helpers extracted and patterns pristine,
The most elegant error flow ever seen! ✨

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: adding decorator support for ServiceErrorHandler, which is the core objective of the pull request.
Description check ✅ Passed The description is directly related to the changeset, explaining the motivation, modifications, and expected results of extending error handler decorators to support ServiceErrorHandler.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java (1)

91-117: Consider making fields private for consistency.

The fields delegate and decoratorFunction in DecoratingServiceErrorHandler (and DecoratingServerErrorHandler above) are package-private. Consider making them private for better encapsulation, since they're only accessed within the class.

♻️ Suggested change
     private static final class DecoratingServiceErrorHandler implements ServiceErrorHandler {

-        final ServiceErrorHandler delegate;
-        final DecoratingErrorHandlerFunction decoratorFunction;
+        private final ServiceErrorHandler delegate;
+        private final DecoratingErrorHandlerFunction decoratorFunction;

         DecoratingServiceErrorHandler(ServiceErrorHandler delegate,

Same applies to DecoratingServerErrorHandler at lines 55-56.


📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e0951b and 8d64d2c.

📒 Files selected for processing (7)
  • core/src/main/java/com/linecorp/armeria/server/ContentPreviewErrorHandler.java
  • core/src/main/java/com/linecorp/armeria/server/CorsErrorHandler.java
  • core/src/main/java/com/linecorp/armeria/server/DecoratingErrorHandlerFunction.java
  • core/src/main/java/com/linecorp/armeria/server/DefaultServerConfig.java
  • core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java
  • core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java
  • core/src/main/java/com/linecorp/armeria/server/ServiceConfigBuilder.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • core/src/main/java/com/linecorp/armeria/server/DefaultServerConfig.java
  • core/src/main/java/com/linecorp/armeria/server/ServiceConfigBuilder.java
  • core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

⚙️ CodeRabbit configuration file

**/*.java: - The primary coding conventions and style guide for this project are defined in site/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.

2. Specific check for @UnstableApi

  • Review all newly added public classes and methods to ensure they have the @UnstableApi annotation.
  • However, this annotation is NOT required under the following conditions:
    • If the class or method is located in a package containing .internal or .testing.
    • If the class or method is located in a test source set.
    • If a public method is part of a class that is already annotated with @UnstableApi.

Files:

  • core/src/main/java/com/linecorp/armeria/server/ContentPreviewErrorHandler.java
  • core/src/main/java/com/linecorp/armeria/server/DecoratingErrorHandlerFunction.java
  • core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java
  • core/src/main/java/com/linecorp/armeria/server/CorsErrorHandler.java
🧬 Code graph analysis (1)
core/src/main/java/com/linecorp/armeria/server/CorsErrorHandler.java (1)
core/src/main/java/com/linecorp/armeria/internal/server/CorsHeaderUtil.java (1)
  • CorsHeaderUtil (43-216)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Kubernetes Chaos test
  • GitHub Check: build-ubicloud-standard-16-jdk-21-snapshot-blockhound
  • GitHub Check: site
  • GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-17-coverage
  • GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-11
  • GitHub Check: build-windows-latest-jdk-25
  • GitHub Check: build-ubicloud-standard-16-jdk-25
  • GitHub Check: build-macos-latest-jdk-25
  • GitHub Check: build-ubicloud-standard-16-jdk-17-leak
  • GitHub Check: build-ubicloud-standard-16-jdk-11
  • GitHub Check: build-ubicloud-standard-16-jdk-8
  • GitHub Check: flaky-tests
  • GitHub Check: lint
  • GitHub Check: Summary
🔇 Additional comments (4)
core/src/main/java/com/linecorp/armeria/server/DecoratingErrorHandlerFunction.java (1)

22-33: LGTM!

The interface design is clean and symmetric, providing consistent decoration patterns for both ServerErrorHandler and ServiceErrorHandler. The documentation clearly explains the purpose.

core/src/main/java/com/linecorp/armeria/server/ContentPreviewErrorHandler.java (1)

25-60: LGTM!

Good extraction of maybeSetUpResponseContentPreviewer to eliminate code duplication. Both overloads correctly apply content preview setup after the delegate processes the exception, ensuring the response is captured for previewing.

core/src/main/java/com/linecorp/armeria/server/CorsErrorHandler.java (1)

31-83: LGTM!

Clean extraction of maybeSetCorsHeaders to eliminate duplication. CORS headers are correctly set before the delegate call via ctx.mutateAdditionalResponseHeaders, ensuring they're applied to all error responses regardless of how the delegate processes the exception.

core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java (1)

45-51: LGTM!

The new decorate(ServiceErrorHandler) method mirrors the existing decorate(ServerErrorHandler) pattern, ensuring consistent decorator application for both error handler types. This addresses the PR's goal of applying CORS and content preview decorators to ServiceErrorHandler instances.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Dec 30, 2025

Codecov Report

❌ Patch coverage is 97.29730% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 74.38%. Comparing base (8150425) to head (3e0951b).
⚠️ Report is 316 commits behind head on main.

Files with missing lines Patch % Lines
.../com/linecorp/armeria/server/CorsErrorHandler.java 90.90% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6570      +/-   ##
============================================
- Coverage     74.46%   74.38%   -0.08%     
- Complexity    22234    23714    +1480     
============================================
  Files          1963     2128     +165     
  Lines         82437    88614    +6177     
  Branches      10764    11587     +823     
============================================
+ Hits          61385    65914    +4529     
- Misses        15918    17165    +1247     
- Partials       5134     5535     +401     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! 👍👍

@ikhoon ikhoon added the defect label Dec 30, 2025
@ikhoon ikhoon added this to the 1.35.0 milestone Dec 30, 2025
Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, left a minor question

static ServerErrorHandler decorate(ServerErrorHandler delegate) {
ServerErrorHandler decorated = delegate;
for (DecoratingServerErrorHandlerFunction decoratorFunction : decoratorFunctions) {
for (DecoratingErrorHandlerFunction decoratorFunction : decoratorFunctions) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question) Since the ServiceErrorHandler is decorated, is there still a need to decorate the ServerErrorHandler?

e.g. the following diff seems to still pass the test:

user@AL02437565 upstream-armeria % git diff
diff --git a/core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java b/core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java
index faa2867cf..a614d3af0 100644
--- a/core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java
+++ b/core/src/main/java/com/linecorp/armeria/server/ErrorHandlerDecorators.java
@@ -35,11 +35,7 @@ final class ErrorHandlerDecorators {
     private ErrorHandlerDecorators() {}
 
     static ServerErrorHandler decorate(ServerErrorHandler delegate) {
-        ServerErrorHandler decorated = delegate;
-        for (DecoratingErrorHandlerFunction decoratorFunction : decoratorFunctions) {
-            decorated = new DecoratingServerErrorHandler(decorated, decoratorFunction);
-        }
-        return decorated;
+        return delegate;
     }
 
     static ServiceErrorHandler decorate(ServiceErrorHandler delegate) {
user@AL02437565 upstream-armeria %

The concern is that implementors of the default ErrorHandlerDecorators#decoratorFunctions need to be aware that each DecoratingErrorHandlerFunction may be applied twice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good finding. I think we need to remove the decorating here:

final ServerErrorHandler errorHandler = ErrorHandlerDecorators.decorate(

and add it here instead:
this.errorHandler = requireNonNull(errorHandler, "errorHandler");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!


Regardless, I found another edge case where the content preview is not recorded. An HttpResponse can be created directly via renderStatus(...) when it is an AbortedHttpResponse. If a service returns HttpResponse.ofFailure(HttpResponseException(...)) without an error handler, the error response is created using renderStatus(...) directly:

} else if (peeled instanceof HttpStatusException) {
final Throwable cause0 = firstNonNull(peeled.getCause(), peeled);
final AggregatedHttpResponse res = toAggregatedHttpResponse((HttpStatusException) peeled);
failAndRespond(cause0, res, Http2Error.CANCEL, false);

final AggregatedHttpResponse toAggregatedHttpResponse(HttpStatusException cause) {
final HttpStatus status = cause.httpStatus();
final Throwable cause0 = firstNonNull(cause.getCause(), cause);
final ServiceConfig serviceConfig = reqCtx.config();
final AggregatedHttpResponse response = serviceConfig.errorHandler()
.renderStatus(reqCtx, req.headers(), status,
null, cause0);
assert response != null;
return response;
}

Because onServiceException(...) is not called with the returned response, the request log does not complete. My idea is to apply the same decorating logic to the response returned from the delegate’s renderStatus(...).

I’m unsure whether there is any chance the decorator could be applied twice. For example, via renderStatus(...) -> FallbackService -> ContentPreviewingService or CorsService.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, there's definitely an edge case here.
Plus, it looks like we're generating a content preview even when a preceding decorator fails, which we shouldn't be doing.
What do you think about preventing the preview from being generated whenever an exception is raised?
Since we already set the preview to null on an exception in DefaultRequestLog, this seems like the right fix.

// Will auto-fill response content and its preview if response has failed.
deferredFlags = this.deferredFlags & ~(RESPONSE_CONTENT.flag() |
RESPONSE_CONTENT_PREVIEW.flag());

I think we could set the preview to null at these two spots to solve it:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plus, it looks like we're generating a content preview even when a preceding decorator fails, which we shouldn't be doing.

Indeed, because content preview generation is done after the response is processed by the recovery step and whether the content preview decorator was actually executed is not considered.

What do you think about preventing the preview from being generated whenever an exception is raised?

If so, should we generate the preview for an aborted response (HttpResponse.ofFailure(...))? I’m a bit confused about what ContentPreviewErrorHandler would do if we choose this behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0x1306e6d It is not a push but are you interested in implementing #6577? Otherwise, I will handle it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, let me try if it doesn't bother you.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 No rush. Take your time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#6589 has been opened instead. I’ll clean up this PR soon and consolidate the changes into ServiceErrorHandler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, this PR focuses on ServiceErrorHandler only.

Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still organizing my thoughts on https://github.com/line/armeria/pull/6570/files#r2655383994.

As this PR is an independent change, let me approve for now

@minwoox minwoox modified the milestones: 1.35.0, 1.36.0 Jan 5, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewingErrorResponseTest.java (1)

203-213: Consider exception type consistency.

The annotated service methods now use IllegalArgumentException (lines 203, 213), while the direct service bindings still use IllegalStateException (lines 66, 72, 103, 121). While this doesn't affect test correctness, using consistent exception types would improve readability and reduce potential confusion.

♻️ Optional: Use consistent exception types

If intentional variety is not required for test coverage, consider standardizing to one exception type:

         @Get("/aborted/unexpected-exception")
         public HttpResponse abortedUnexpectedException() {
-            return HttpResponse.ofFailure(new IllegalArgumentException("Oops!"));
+            return HttpResponse.ofFailure(new IllegalStateException("Oops!"));
         }

         @Get("/throw/unexpected-exception")
         public HttpResponse throwUnexpectedException() {
-            throw new IllegalArgumentException("Oops!");
+            throw new IllegalStateException("Oops!");
         }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d417af7 and 3e0951b.

📒 Files selected for processing (3)
  • core/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.java
  • core/src/main/java/com/linecorp/armeria/server/logging/ContentPreviewingService.java
  • core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewingErrorResponseTest.java
🧰 Additional context used
📓 Path-based instructions (1)
**/*.java

⚙️ CodeRabbit configuration file

**/*.java: - The primary coding conventions and style guide for this project are defined in site/src/pages/community/developer-guide.mdx. Please strictly adhere to this file as the ultimate source of truth for all style and convention-related feedback.

2. Specific check for @UnstableApi

  • Review all newly added public classes and methods to ensure they have the @UnstableApi annotation.
  • However, this annotation is NOT required under the following conditions:
    • If the class or method is located in a package containing .internal or .testing.
    • If the class or method is located in a test source set.
    • If a public method is part of a class that is already annotated with @UnstableApi.

Files:

  • core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewingErrorResponseTest.java
  • core/src/main/java/com/linecorp/armeria/server/logging/ContentPreviewingService.java
  • core/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.java
🧬 Code graph analysis (1)
core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewingErrorResponseTest.java (3)
core/src/main/java/com/linecorp/armeria/server/ServerBuilder.java (1)
  • ServerBuilder (171-2569)
core/src/main/java/com/linecorp/armeria/server/logging/LoggingService.java (1)
  • LoggingService (39-88)
core/src/main/java/com/linecorp/armeria/server/logging/ContentPreviewingService.java (1)
  • ContentPreviewingService (61-183)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-11
  • GitHub Check: build-ubicloud-standard-16-jdk-17-leak
  • GitHub Check: build-macos-latest-jdk-25
  • GitHub Check: build-ubicloud-standard-16-jdk-25
  • GitHub Check: build-windows-latest-jdk-25
  • GitHub Check: build-ubicloud-standard-16-jdk-21-snapshot-blockhound
  • GitHub Check: build-ubicloud-standard-16-jdk-8
  • GitHub Check: build-ubicloud-standard-16-jdk-17-min-java-17-coverage
  • GitHub Check: build-ubicloud-standard-16-jdk-11
  • GitHub Check: site
  • GitHub Check: lint
  • GitHub Check: flaky-tests
  • GitHub Check: Kubernetes Chaos test
  • GitHub Check: Summary
🔇 Additional comments (5)
core/src/main/java/com/linecorp/armeria/server/logging/ContentPreviewingService.java (1)

174-181: LGTM! Properly finalizes deferred log property on exception.

The explicit responseContentPreview(null) call ensures the deferred RESPONSE_CONTENT_PREVIEW property is always completed, even when the service throws an exception. This prevents incomplete logs and aligns with the PR objective to properly handle error responses.

core/src/main/java/com/linecorp/armeria/internal/logging/ContentPreviewingUtil.java (1)

116-120: LGTM! Ensures log completion when previewer is uninitialized.

Explicitly setting responseContentPreview(null) when the previewer is never initialized ensures the deferred log property is always finalized. This handles cases where the response fails before headers are sent, maintaining log consistency across all execution paths.

core/src/test/java/com/linecorp/armeria/server/logging/ContentPreviewingErrorResponseTest.java (3)

41-61: LGTM! Clean dual-server test setup.

The two ServerExtension instances enable comprehensive testing of both scenarios: with and without error handlers. This structure effectively validates that content previews are properly handled in both configurations, which aligns with the PR objective.


84-123: LGTM! Binding routes effectively test ServiceErrorHandler integration.

The binding-based routes with ServiceErrorHandler (lines 84-123) directly validate the core PR objective: ensuring that error handler decorators now apply to ServiceErrorHandler instances. The comprehensive coverage of error scenarios (aborted/throw × http-status/unexpected) ensures the decorator integration works correctly.


129-192: LGTM! Comprehensive parameterized test coverage.

The three test methods effectively validate:

  1. Content preview is null when error handlers are configured (both server and service level)
  2. Content preview is null when no error handlers are present
  3. Annotated service exception handlers properly record content previews

The inclusion of binding routes in test parameters ensures ServiceErrorHandler decorator integration is properly validated, which is the core objective of this PR.

@0x1306e6d 0x1306e6d force-pushed the service-error-handler-decorator branch from 3e0951b to 8d64d2c Compare January 16, 2026 02:43
ikhoon pushed a commit that referenced this pull request Jan 20, 2026
Motivation:

Following the discussion in
#6570 (comment),
content preview was being generated even when a preceding decorator
failed with an exception. This behavior was inconsistent with
`DefaultRequestLog`, which sets the preview to null when an exception is
present:


https://github.com/line/armeria/blob/d068172432163c42908ad5d509262cc4022afb04/core/src/main/java/com/linecorp/armeria/common/logging/DefaultRequestLog.java#L1471-L1473

The content preview should not be recorded when the response flow is
interrupted by an exception, as the decorator did not actually process
the response content.

Modifications:

- Set `responseContentPreview(null)` in `ContentPreviewingUtil` when
`responseContentPreviewer` is null.
- Set `responseContentPreview(null)` in `ContentPreviewingService` when
service execution throws an exception.
- Updated tests to verify null content preview for various exception
scenarios with and without error handlers.

Result:

- Response content preview is properly set to null when exceptions are
raised.
- Consistent behavior between decorator-level exceptions and error
handler responses.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants