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.
-
GeneralExceptionMapper#toResponse builds the response without calling .type(...), so it inherits the resource method's @Produces — application/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
-
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> {
-
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.
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, genericHTTP 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
@Produces(application/xml)resource) get no actionable information on failure — schema/parse errors, mapping errors, validation errors, etc. all collapse to the same body.spring.logon the server, which API consumers generally cannot do.Root cause
The error response is built without an explicit media type, then no
MessageBodyWritermatches it for the response's (inherited)application/xmlcontent type.GeneralExceptionMapper#toResponsebuilds the response without calling.type(...), so it inherits the resource method's@Produces—application/xmlfor the import resource:src/main/java/org/rutebanken/tiamat/rest/exception/GeneralExceptionMapper.java:61-63The only writer for
ErrorResponseEntityis restricted totext/plain, so it does not match anapplication/xmlresponse:src/main/java/org/rutebanken/tiamat/rest/exception/ErrorResponseEntityMessageBodyWriter.java:35-37Result:
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:
Reproduction
POSTany payload that fails during import toPOST /services/stop_places/netex?importType=INITIALwithContent-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:
GeneralExceptionMapper#toResponse, e.g..type(MediaType.TEXT_PLAIN_TYPE)(orapplication/json), so it matches the existing writer; and/orErrorResponseEntityMessageBodyWriter's@Producesto includeapplication/xmlandapplication/json(and serialize accordingly); and/orWhichever 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.