apache/pulsar · error · IllegalArgumentException

expiry time cannot be empty

Error message

expiry time cannot be empty

What it means

RelativeTimeUtil.parseRelativeTimeInSeconds(relativeTime) converts strings like '1000s', '2h', '3d' into seconds. An empty input string cannot denote any time, so it immediately throws IllegalArgumentException('expiry time cannot be empty'). It is a fail-fast input validation error thrown before any parsing.

Source

Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/util/RelativeTimeUtil.java:33

 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.pulsar.common.util;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.concurrent.TimeUnit;
import lombok.experimental.UtilityClass;

/**
 * Parser for relative time.
 */
@UtilityClass
public class RelativeTimeUtil {
    public static long parseRelativeTimeInSeconds(String relativeTime) {
        if (relativeTime.isEmpty()) {
            throw new IllegalArgumentException("expiry time cannot be empty");
        }

        int lastIndex =  relativeTime.length() - 1;
        char lastChar = relativeTime.charAt(lastIndex);
        final char timeUnit;

        if (!Character.isAlphabetic(lastChar)) {
            // No unit specified, assume seconds
            timeUnit = 's';
            lastIndex = relativeTime.length();
        } else {
            timeUnit = Character.toLowerCase(lastChar);
        }

        long duration = Long.parseLong(relativeTime.substring(0, lastIndex));

        switch (timeUnit) {
        case 's':

View on GitHub (pinned to 820761864e)

Solutions

  1. Set the configuration value to a valid relative time like '3600s', '24h', or '7d'.
  2. Remove/comment out the empty property so Pulsar applies its default instead of an empty string.
  3. Guard with a default before parsing, e.g. StringUtils.isBlank(value) ? fallback : parseRelativeTimeInSeconds(value).
  4. Use a supported unit suffix (s/m/h/d/w) — note the empty check happens first, then unit validation.

Example fix

// before
String cfg = config.get("expiry"); // ""
long secs = RelativeTimeUtil.parseRelativeTimeInSeconds(cfg);
// after
String cfg = config.get("expiry");
long secs = (cfg == null || cfg.isEmpty()) ? defaultSeconds : RelativeTimeUtil.parseRelativeTimeInSeconds(cfg);
Defensive patterns

Strategy: validation

Validate before calling

static long safeParse(String cfg, long defaultSeconds) {
    if (cfg == null || cfg.isEmpty()) return defaultSeconds;
    return RelativeTimeUtil.parseRelativeTimeInSeconds(cfg);
}

Type guard

static boolean isValidRelativeTime(String s) {
    if (s == null || s.isEmpty()) return false;
    char u = s.charAt(s.length() - 1);
    return "smhdw".indexOf(u) >= 0 && s.substring(0, s.length() - 1).chars().allMatch(Character::isDigit);
}

Try / catch

try { return RelativeTimeUtil.parseRelativeTimeInSeconds(relativeTime); }
catch (IllegalArgumentException e) {
    if (relativeTime == null || relativeTime.isEmpty())
        throw new IllegalStateException("Configured expiry time is empty; set e.g. '3600s' or remove the property to use the default", e);
    throw e;
}

Prevention

When it happens

Trigger: Calling RelativeTimeUtil.parseRelativeTimeInSeconds("") — typically from a broker configuration value (e.g. ttl or expiry-style settings) that was left blank.

Common situations: broker.conf / client config key present but with an empty value; environment variable expansion yielding an empty string; CLI flag supplied without a value; YAML property defined with no value.

Understand the failure class

Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/9272df6d5bcb3e33. Report an issue: GitHub.