USACE / USACE/cwms-data-api

Adding JSON to BLOB description causes an error 500

Open
#1,698 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug priority:highest
Dominant language
Java
Stars
28
Forks
25
Avg merge
4d 22h
Merged PRs (30d)
20

Description

Describe the bug
If you try to save JSON (not a string, but a serialized JSON object) to the description field when storing a BLOB, an error 500 is returned, and the log contains a serialization error.

Expected behavior
Either ignoring the deserialization of that field, or capture and return a more appropriate error message.

To Reproduce

    public static void storeBinary(String name, byte[] object, String mime_type, String comment, URL cdaUrl, String cdaKey, String cdaOffice) throws IOException, InterruptedException {

        JsonObject desc = new JsonObject();
        desc.put("last-modified", Instant.now().toEpochMilli());
        if(comment != null && !comment.equals(""))
            desc.put("comment", comment);

        JsonObject body = new JsonObject();
        body.put("office-id", cdaOffice);
        body.put("id", name);
        body.put("media-type-id", mime_type);
        body.put("description", desc);
        body.put("value", Base64.getMimeEncoder().encodeToString(object));

        var client = HttpClient.newHttpClient();
        var request =  HttpRequest.newBuilder()
                        .uri(URI.create(cdaUrl.toString() + "/blobs?fail-if-exists=false"))
                        .header("Authorization", "apikey " + cdaKey)
                        .POST(BodyPublishers.ofString(body.toJson()))
                        .build();

        logger.info("Storing binary in CDA: " + name);
        var response = client.send(request, BodyHandlers.ofString());
        if(response.statusCode() != 200)
        {
            throw new IOException("Error saving binary to " + request.uri().toString() + ": " + response.statusCode() + " " + response.body());
        }
    }

Logs/Incident Identifier

17-Apr-2026 18:04:15.170 SEVERE [ajp-nio-127.0.0.1-8009-exec-900] cwms.cda.ApiServlet.lambda$init$3 eb444fa7-ae59-475e-a1e8-3d0ac486c20d: Formatting error:Could not deserialize: org.apache.catalina.connector.CoyoteInputStream@7194fc46 of
 type: class cwms.cda.data.dto.Blob. Originates from Parser
        cwms.cda.formatters.FormattingException: Could not deserialize: org.apache.catalina.connector.CoyoteInputStream@7194fc46 of type: class cwms.cda.data.dto.Blob
                at cwms.cda.formatters.json.JsonV2.parseContent(JsonV2.java:117)
                at cwms.cda.formatters.Formats.parseContentFromType(Formats.java:165)
                at cwms.cda.formatters.Formats.parseContent(Formats.java:241)
                at cwms.cda.api.BlobController.create(BlobController.java:206)
                at io.javalin.apibuilder.CrudFunction$3.invoke$lambda-0(CrudHandler.kt:32)
                at io.javalin.apibuilder.CrudFunctionHandler.handle(CrudHandler.kt)
                at cwms.cda.security.CdaAccessManager.manage(CdaAccessManager.java:37)
                at io.javalin.http.JavalinServlet.addHandler$lambda-0(JavalinServlet.kt:96)
                at io.javalin.http.JavalinServlet$lifecycle$2$1$1.invoke(JavalinServlet.kt:43)
                at io.javalin.http.JavalinServlet$lifecycle$2$1$1.invoke(JavalinServlet.kt:43)
                at io.javalin.http.JavalinServletHandler.executeNextTask(JavalinServletHandler.kt:99)
                at io.javalin.http.JavalinServletHandler.queueNextTaskOrFinish$lambda-1(JavalinServletHandler.kt:85)
                at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1106)
                at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2235)
                at io.javalin.http.JavalinServletHandler.queueNextTaskOrFinish$javalin(JavalinServletHandler.kt:85)
                at io.javalin.http.JavalinServletHandler.executeNextTask$lambda-11$lambda-10(JavalinServletHandler.kt:119)
                at java.base/java.util.concurrent.CompletableFuture.uniApplyNow(CompletableFuture.java:680)
                at java.base/java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:658)
                at java.base/java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:2094)
                at io.javalin.http.JavalinServletHandler.executeNextTask(JavalinServletHandler.kt:119)
                at io.javalin.http.JavalinServletHandler.queueNextTaskOrFinish$lambda-1(JavalinServletHandler.kt:85)
                at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1106)
                at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2235)
                at io.javalin.http.JavalinServletHandler.queueNextTaskOrFinish$javalin(JavalinServletHandler.kt:85)
                at io.javalin.http.JavalinServletHandler.executeNextTask$lambda-11$lambda-10(JavalinServletHandler.kt:119)
                at java.base/java.util.concurrent.CompletableFuture.uniApplyNow(CompletableFuture.java:680)
                at java.base/java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:658)
                at java.base/java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:2094)
                at io.javalin.http.JavalinServletHandler.executeNextTask(JavalinServletHandler.kt:119)
                at io.javalin.http.JavalinServletHandler.queueNextTaskOrFinish$lambda-1(JavalinServletHandler.kt:85)
                at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1106)
                at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2235)
                at io.javalin.http.JavalinServletHandler.queueNextTaskOrFinish$javalin(JavalinServletHandler.kt:85)
                at io.javalin.http.JavalinServlet.service(JavalinServlet.kt:89)
                at javax.servlet.http.HttpServlet.service(HttpServlet.java:623)
                at cwms.cda.ApiServlet.service(ApiServlet.java:920)
                at javax.servlet.http.HttpServlet.service(HttpServlet.java:623)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:197)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:142)
                at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:166)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:142)
                at org.apache.catalina.filters.HttpHeaderSecurityFilter.doFilter(HttpHeaderSecurityFilter.java:129)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:166)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:142)
                at org.apache.catalina.filters.CorsFilter.handleNonCORS(CorsFilter.java:334)
                at org.apache.catalina.filters.CorsFilter.doFilter(CorsFilter.java:161)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:166)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:142)
                at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166)
                at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:88)
                at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:596)
                at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
                at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:643)
                at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:643)
                at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:83)
                at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:83)
                at org.apache.catalina.authenticator.SingleSignOn.invoke(SingleSignOn.java:240)
                at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:72)
                at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
                at org.apache.coyote.ajp.AjpProcessor.service(AjpProcessor.java:429)
                at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
                at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:935)
                at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1831)
                at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
                at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:973)
                at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:491)
                at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
                at java.base/java.lang.Thread.run(Thread.java:834)
        Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 34] (through reference chain: cwms.cda.data.dto.Blob["description"])
                at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
                at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1767)
                at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1541)
                at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1446)
                at com.fasterxml.jackson.databind.DeserializationContext.extractScalarFromObject(DeserializationContext.java:958)
                at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseString(StdDeserializer.java:1441)
                at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:48)
                at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:11)
                at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
                at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:310)
                at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
                at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342)
                at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4905)
                at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3885)
                at cwms.cda.formatters.json.JsonV2.parseContent(JsonV2.java:115)
                ... 68 more

CDA Version (please complete the following information):
At the top of any instance of CDA you will see a version. Please provide that version.

  • Version 2023.03.31

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with cwms.cda.formatters.json.JsonV2.parseContent and the cwms.cda.data.dto.Blob field named in the deserialization error, then trace the request through BlobController.create. Reproduce the JSON description case from the issue and inspect the existing formatting error handling. Done means the request no longer produces an unexpected 500 and follows one of the documented expected behaviors.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.