URL decode a string but log an error after second exception
First, let's  move the replacements into a separate fixErrors method for readability.

String fixErrors(String value) {
    return value
        .replaceAll("%(?![0-9a-fA-F]{2})", "%25")
        .replaceAll("\\+", "%2B");
}


Now, my solution would be to write a method that returns the result of URLDecoder.decode as an Optional. Then, instead of nesting try/catch statements, we can just use Optional::or. This should look something like

public String decodeValue(String value, Charset charset) {
    if (!Strings.isNullOrEmpty(value)) {
        return tryDecode(value, charset)
            .or(() -&gt; tryDecode(fixErrors(value), charset))
            .orElse(null);
    }
    return value;
}

Optional&lt;String&gt; tryDecode(String value, Charset charset) {
    try {
        return Optional.of(URLDecoder.decode(value, charset.name()));
    } catch (UnsupportedEncodingException ex) {
        return Optional.empty();
    }
}


Unfortunately, this solution precludes logging details of only the second error. To do this effectively, we could add error handling to tryDecode by passing it a Consumer&lt;UnsupportedEncodingException&gt;. Then we would have:

public String decodeValue(String value, Charset charset) {
    if (!Strings.isNullOrEmpty(value)) {
        return tryDecode(value, charset, ex -&gt; {})
        .or(() -&gt; tryDecode(fixErrors(value), charset, ex -&gt; {
                // log ex
            }))
        .orElse(null);
    }
    return value;
}

Optional&lt;String&gt; tryDecode(String value, Charset charset,
                   Consumer&lt;UnsupportedEncodingException&gt; exConsumer) {
    try {
        return Optional.of(URLDecoder.decode(value, charset.name()));
    } catch (UnsupportedEncodingException ex) {
        exConsumer.accept(ex);
        return Optional.empty();
    }
}


Hopefully this solution meets your requirements.