Skip to content

RUN-2569: Fix ClassCastException in HttpBuilder.setHeaders for non-String header values#42

Open
ronaveva wants to merge 1 commit into
masterfrom
RUN-2569
Open

RUN-2569: Fix ClassCastException in HttpBuilder.setHeaders for non-String header values#42
ronaveva wants to merge 1 commit into
masterfrom
RUN-2569

Conversation

@ronaveva
Copy link
Copy Markdown

Summary

  • HttpBuilder.setHeaders parsed header config via Gson/SnakeYAML, which return Map<String, Object> with non-String values (e.g. Integer, Boolean) for numeric/boolean scalars
  • Iterating the result as Map.Entry<String, String> compiled due to type erasure but threw ClassCastException at runtime (e.g. {"Content-Length": 0})
  • Refactored setHeaders to use a parseHeaders helper returning Map<String, Object> with explicit instanceof Map checks, and a headerValueToString helper that coerces all values to String safely
  • Whole-number Double values (Gson parses all JSON numbers as Double) are rendered via Long.toString so {"Content-Length": 0} emits "0" instead of "0.0"
  • Non-scalar values (lists, nested maps) are stringified via String.valueOf

Test plan

  • ./gradlew clean build passes (Java 17)
  • HttpBuilderTest — 14/14 tests green, covering: YAML/JSON integer, plain string, mixed types, boolean, list stringification, unparseable input log path, and direct tests of headerValueToString
  • Verify HTTP step jobs that use numeric header values (e.g. Content-Length, Retry-After) run without ClassCastException on Rundeck 5.3.0+

References

🤖 Generated with Claude Code

…569)

HttpBuilder.setHeaders parsed the headers config string with Gson, then
fell back to SnakeYAML. Both libraries place non-String values into the
resulting map for JSON/YAML numeric and boolean scalars. The map was then
iterated with Map.Entry<String,String>, which compiled because of generic
type erasure but threw ClassCastException at runtime when the value was
e.g. Integer (the case reported in
#32 and PagerDuty
ticket RUN-2569).

Restructure setHeaders to delegate parsing to a private parseHeaders
helper that returns Map<String,Object> after explicit `instanceof Map`
checks (no more "exceptions as control flow"), and route every value
through a new headerValueToString helper. The helper renders whole-number
Double values via Long.toString so JSON like {"Content-Length":0} emits
"0" instead of "0.0" (Gson parses every JSON number as Double when the
target type is HashMap.class). Non-scalar values (lists, nested maps)
are stringified via String.valueOf instead of throwing.

Adds unit tests in HttpBuilderTest covering: YAML/JSON integer, plain
string, mixed types, boolean, list stringification, unparseable input
log path, and direct tests of headerValueToString for whole-number and
fractional Doubles.

./gradlew clean build green on Java 17; HttpBuilderTest 14/14.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings May 19, 2026 16:01
@ronaveva ronaveva added this to the 6.1.0 milestone May 19, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a runtime ClassCastException in HttpBuilder.setHeaders when header configuration is parsed from YAML/JSON into non-String scalar values (e.g. integers/booleans), and adds targeted tests to pin the corrected behavior.

Changes:

  • Refactors setHeaders to parse into Map<String, Object> and stringify header values safely via headerValueToString.
  • Adds parseHeaders helper to accept either JSON or YAML maps and avoid crashing on non-map inputs.
  • Expands HttpBuilderTest coverage for YAML/JSON numeric/boolean/list header values and unparseable input behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/main/java/edu/ohio/ais/rundeck/HttpBuilder.java Refactors header parsing/coercion to avoid ClassCastException for non-String header values.
src/test/java/edu/ohio/ais/rundeck/HttpBuilderTest.java Adds regression/unit tests covering numeric/boolean/list header values and parsing failure behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +437 to +439
for (Map.Entry<String, Object> entry : map.entrySet()) {
request.setHeader(entry.getKey(), headerValueToString(entry.getValue()));
}
Comment on lines +463 to 472
static String headerValueToString(Object value) {
if (value instanceof Double) {
double d = (Double) value;
if (!Double.isInfinite(d) && !Double.isNaN(d) && d == Math.floor(d)
&& d >= Long.MIN_VALUE && d <= Long.MAX_VALUE) {
return Long.toString((long) d);
}
}
return String.valueOf(value);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants