Skip to content

REST/import errors on XML endpoints masked as opaque HTTP 500 (real cause discarded) #403

Description

@assadriaz

Summary

Any exception thrown while handling a request on an XML-producing REST endpoint (e.g. the NeTEx import endpoint POST /services/stop_places/netex) is returned to the client as an opaque, generic HTTP 500:

{"timestamp":"","status":500,"error":"Internal Server Error","path":"/services/stop_places/netex"}

The real error message is discarded and is only visible in the server log. This makes every import/validation failure look identical from the client side and significantly slows down diagnosis.

Impact

  • Callers of the import API (and any other @Produces(application/xml) resource) get no actionable information on failure — schema/parse errors, mapping errors, validation errors, etc. all collapse to the same body.
  • The cause can only be recovered by reading spring.log on the server, which API consumers generally cannot do.
  • This masking is what hid four separate, distinct import failures during a recent investigation (each surfaced as the same generic 500). See the companion issue about non-3-part NeTEx IDs.

Root cause

The error response is built without an explicit media type, then no MessageBodyWriter matches it for the response's (inherited) application/xml content type.

  1. GeneralExceptionMapper#toResponse builds the response without calling .type(...), so it inherits the resource method's @Producesapplication/xml for the import resource:

    src/main/java/org/rutebanken/tiamat/rest/exception/GeneralExceptionMapper.java:61-63

    return Response.status(status)
                   .entity(new ErrorResponseEntity(rootCause.getMessage()))
                   .build();   // no .type(...) → inherits application/xml
  2. The only writer for ErrorResponseEntity is restricted to text/plain, so it does not match an application/xml response:

    src/main/java/org/rutebanken/tiamat/rest/exception/ErrorResponseEntityMessageBodyWriter.java:35-37

    @Provider
    @Produces("text/plain")
    public class ErrorResponseEntityMessageBodyWriter implements MessageBodyWriter<ErrorResponseEntity> {
  3. Result: MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=application/xml;charset=UTF-8, type=…ErrorResponseEntity. Jersey logs this secondary error and falls back to the default 500 handler — the original cause never reaches the body.

Observed in logs:

ERROR o.g.j.m.i.WriterInterceptorExecutor : MessageBodyWriter not found for media type=application/xml;charset=UTF-8,
       type=class org.rutebanken.tiamat.rest.exception.ErrorResponseEntity …
ERROR o.g.j.server.ServerRuntime$Responder : Error occurred when processing a response created from an already mapped exception.

Reproduction

POST any payload that fails during import to POST /services/stop_places/netex?importType=INITIAL with Content-Type: application/xml (e.g. a NeTEx file that triggers a mapping/parse exception). The response is the generic {"status":500,"error":"Internal Server Error"} with no cause; the actual exception is only in the log.

Suggested fix

Make the error body serializable for the content types these endpoints actually produce. Any of:

  • Set the media type explicitly in GeneralExceptionMapper#toResponse, e.g. .type(MediaType.TEXT_PLAIN_TYPE) (or application/json), so it matches the existing writer; and/or
  • Broaden ErrorResponseEntityMessageBodyWriter's @Produces to include application/xml and application/json (and serialize accordingly); and/or
  • Return a problem+json / structured error consistently across REST endpoints.

Whichever approach, the goal is that the original error message reaches the client instead of being dropped.


Found while investigating a NeTEx import failure. Code references are against master @ d4cdae850.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions