signalapp/Signal-Server · warning · UnrecognizedUserAgentException

User-Agent string is blank

Error message

User-Agent string is blank

What it means

parseUserAgentString throws UnrecognizedUserAgentException with "User-Agent string is blank" when the incoming User-Agent header is null, empty, or whitespace-only. Signal-Server only recognizes User-Agent strings matching the pattern Signal-(Android|Desktop|iOS)/<version>; a blank string cannot even be attempted against the pattern, so it fails fast with a dedicated exception.

Solutions

  1. Have the client send a compliant User-Agent, e.g. "Signal-Android/7.0.0", "Signal-Desktop/6.30.0", or "Signal-iOS/7.0.5".
  2. If blank UAs are acceptable in your context, call maybeParseUserAgentString (or catch UnrecognizedUserAgentException) and fall back to a default/unknown UserAgent.
  3. Check proxy/load-balancer config so the User-Agent header is forwarded rather than stripped.
  4. For scripts/tests, set the header explicitly: -H "User-Agent: Signal-Desktop/6.30.0".
  5. Treat the exception as a signal to request a client update if your service requires version reporting.

Example fix

// before
curl -s https://server/api/v1/...           # no User-Agent header -> blank
// after
curl -s -H "User-Agent: Signal-Android/7.0.0" https://server/api/v1/...
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling
if (userAgentHeader == null || userAgentHeader.isBlank()) {
    userAgent = UserAgentUtil.UNKNOWN_USER_AGENT; // or reject the request
} else {
    userAgent = UserAgentUtil.parseUserAgentString(userAgentHeader);
}

Type guard

boolean hasUserAgent(String ua) {
    return ua != null && !ua.isBlank();
}

Try / catch

UserAgent ua;
try {
    ua = UserAgentUtil.parseUserAgentString(request.getHeader("User-Agent"));
} catch (UnrecognizedUserAgentException e) {
    if (e.getMessage().contains("blank")) {
        ua = UserAgentUtil.UNKNOWN_USER_AGENT; // or return 400 if UA is mandatory
    } else {
        ua = UserAgentUtil.UNKNOWN_USER_AGENT;
    }
}

Prevention

When it happens

Trigger: Calling UserAgentUtil.parseUserAgentString(null), parseUserAgentString(""), or parseUserAgentString(" ") — typically from maybeParseUserAgentString when a client sent no User-Agent header at all.

Common situations: Curl/HTTP client calls made without a User-Agent header; stripped headers by load balancers, proxies, or privacy tooling; internal health checks and service-to-service gRPC/HTTP calls that omit the header; bot or script traffic.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/dc652fda7096fa50. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/util/ua/UserAgentUtil.java:21

 * SPDX-License-Identifier: AGPL-3.0-only
 */

package org.whispersystems.textsecuregcm.util.ua;

import com.vdurmont.semver4j.Semver;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nullable;

public class UserAgentUtil {

  private static final Pattern STANDARD_UA_PATTERN = Pattern.compile("^Signal-(Android|Desktop|iOS)/([^ ]+)( (.+))?$", Pattern.CASE_INSENSITIVE);

  public static UserAgent parseUserAgentString(final String userAgentString) throws UnrecognizedUserAgentException {
    if (StringUtils.isBlank(userAgentString)) {
      throw new UnrecognizedUserAgentException("User-Agent string is blank");
    }

    try {
      final Matcher matcher = STANDARD_UA_PATTERN.matcher(userAgentString);

      if (matcher.matches()) {
        return new UserAgent(ClientPlatform.valueOf(matcher.group(1).toUpperCase()), new Semver(matcher.group(2)), StringUtils.stripToNull(matcher.group(4)));
      }
    } catch (final Exception e) {
      throw new UnrecognizedUserAgentException(e);
    }

    throw new UnrecognizedUserAgentException();
  }

  public static @Nullable UserAgent maybeParseUserAgentString(final String userAgentString) {
    try {
      return parseUserAgentString(userAgentString);

View on GitHub (pinned to 100ab61c82)