I’m using AWS API Gateway HTTP API v2 with a Lambda function running a Java Spring Boot application
The integration type is Lambda proxy integration with payloadFormatVersion: 2.0.
Everything works fine, except that query parameters get modified by API Gateway before reaching my Spring Boot app.
The problem
When I call this endpoint:
GET https://api.example.com/welcome?param1=aGVsbG8gbXkgbmFtZSBpcyBqb2huCg==
What I see in logs
DEBUG --- Securing GET /welcome?param1=aGVsbG8gbXkgbmFtZSBpcyBqb2huCg==
DEBUG --- GET "/welcome?param1=aGVsbG8gbXkgbmFtZSBpcyBqb2huCg=="
INFO --- preHandle: method=GET, uri=/welcome, parameters=[param1=aGVsbG8gbXkgbmFtZSBpcyBqb2huCg]
As you can see, the request arrives with the correct value (== present),
but by the time Spring handles it, the padding is missing.
What I tried
To work around this issue, I added a filter that attempts to automatically restore the missing Base64 padding when needed:
@Component
@Order(HIGHEST_PRECEDENCE)
public class Base64PaddingFixFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
var originalParams = request.getParameterMap();
var fixedParams = originalParams.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> Arrays.stream(e.getValue())
.map(Base64PaddingFixFilter::padBase64IfNeeded)
.toArray(String[]::new)));
var hasModifiedParams = !fixedParams.equals(originalParams);
if (hasModifiedParams) {
var wrapped = new FixedParamsRequestWrapper(request, fixedParams);
filterChain.doFilter(wrapped, response);
} else {
filterChain.doFilter(request, response);
}
}
private static String padBase64IfNeeded(String s) {
if (s == null) return null;
var len = s.length();
if (len == 0) return s;
var base64UrlPattern = "^[A-Za-z0-9_\\-]+=*$";
if (!s.matches(base64UrlPattern)) return s;
var mod = len % 4;
if (mod == 0) return s;
var pad = 4 - mod;
return s + "=".repeat(pad);
}
private static class FixedParamsRequestWrapper extends HttpServletRequestWrapper {
private final Map<String, String[]> fixedParams;
public FixedParamsRequestWrapper(HttpServletRequest request, Map<String, String[]> fixedParams) {
super(request);
this.fixedParams = fixedParams;
}
@Override public String getParameter(String name) {
var values = fixedParams.get(name);
return (values != null && values.length > 0) ? values[0] : null;
}
@Override public Map<String, String[]> getParameterMap() { return fixedParams; }
@Override public String[] getParameterValues(String name) { return fixedParams.get(name); }
}
}
This works for Base64 parameters — it detects and restores missing = characters.
The problem with this workaround
If a query parameter is not Base64, but happens to match the Base64 pattern ([A-Za-z0-9_-]+=*),
the filter incorrectly adds padding to it, treating it as a “corrupted” Base64 value.
Example:
GET /welcome?param1=abc
becomes:
param1=abc=
which is obviously not correct.
So this approach is unreliable unless all query parameters are guaranteed to be Base64-encoded.
Questions
Why does API Gateway HTTP API v2 remove or alter the padding (
=) in query parameters before invoking the Lambda?Is there a way to configure API Gateway to preserve query parameters exactly as sent (no decoding/re-encoding)?
Is there a more reliable workaround than inspecting and re-padding parameters manually ?
https://api.example.com/welcome?param1=aGVsbG8gbXkgbmFtZSBpcyBqb2huCg%3D%3DaGVsbG8gbXkgbmFtZSBpcyBqb2huCg==). Because of that, AWS API Gateway can cut off or change the query parameters, so by the time they reach my Spring Boot backend, the state doesn’t match anymore and the OAuth2 flow breaks. One idea I have is to grab the raw request before it gets parsed and manually re-encode the state and other values, but I’m not sure if that’s even possible.