Tamas Szekeres opened SPR-16232 and commented

The Tokenizer cuts the integer literal -2147483648 (whose value is equal to Integer.MIN_VALUE) into a MINUS token and a LITERAL_INT. The integer token, in itself, falls outside of the integer range, so from that point of view it is understandable that the parsing later fails, but from a usability one, it should work.

public void testParseWhenMinIntegerSetShouldNotBeThrowingExceptionButItDoes() {
    final ExpressionParser parser = new SpelExpressionParser();
    final Expression expression = parser.parseExpression(String.valueOf(Integer.MIN_VALUE));
    expression.getValue();
}
FAILED: testParseWhenMinIntegerSetShouldNotBeThrowingExceptionButItDoes
org.springframework.expression.spel.SpelParseException: EL1035E: The value '2147483648' cannot be parsed as an int
    at org.springframework.expression.spel.ast.Literal.getIntLiteral(Literal.java:80)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.maybeEatLiteral(InternalSpelExpressionParser.java:840)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatStartNode(InternalSpelExpressionParser.java:500)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatPrimaryExpression(InternalSpelExpressionParser.java:343)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatUnaryExpression(InternalSpelExpressionParser.java:337)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatUnaryExpression(InternalSpelExpressionParser.java:316)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatPowerIncDecExpression(InternalSpelExpressionParser.java:293)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatProductExpression(InternalSpelExpressionParser.java:272)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatSumExpression(InternalSpelExpressionParser.java:255)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatRelationalExpression(InternalSpelExpressionParser.java:210)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatLogicalAndExpression(InternalSpelExpressionParser.java:198)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatLogicalOrExpression(InternalSpelExpressionParser.java:186)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.eatExpression(InternalSpelExpressionParser.java:146)
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.doParseExpression(InternalSpelExpressionParser.java:127)
    at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:60)
    at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:32)
    at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:73)
    at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:60)
    ...
Caused by: java.lang.NumberFormatException: For input string: "2147483648"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:499)
    at org.springframework.expression.spel.ast.Literal.getIntLiteral(Literal.java:76)
    ... 44 more

Affects: 4.3.11, 4.3.12

Comment From: sbrannen

First and foremost, thank you for the detailed report and apologies for taking so long to process this issue.

Please note that, depending on your exact use case, you may be able to work around this limitation if you're using the StandardEvaluationContext (and not the SimpleEvaluationContext) by making use of the type operator T() and Integer.valueOf(String) as demonstrated in the following test which passes.

@Test
void parsingIntegerMinValueFromStringLiteral() {
    EvaluationContext context = new StandardEvaluationContext();
    ExpressionParser parser = new SpelExpressionParser();
    Expression expression = parser.parseExpression("T(Integer).valueOf('-2147483648')");
    assertThat(expression.getValue(context)).isEqualTo(Integer.MIN_VALUE);
}

Comment From: sbrannen

An alternative would be to represent the Integer.MIN_VALUE literal value as a Long instead of an Integer as follows, in case that works for your use case.

@Test
void parsingIntegerMinValueFromLongLiteral() {
    EvaluationContext context = new StandardEvaluationContext();
    ExpressionParser parser = new SpelExpressionParser();
    Expression expression = parser.parseExpression("-2147483648L");
    assertThat(expression.getValue(context, Integer.class)).isEqualTo(Integer.MIN_VALUE);
}

Note, however, that the same limitation applies to Long.MIN_VALUE when used as a long literal within a SpEL expression. The following test fails accordingly.

@Test
void parsingLongMinValueFromLongLiteral() {
    EvaluationContext context = new StandardEvaluationContext();
    ExpressionParser parser = new SpelExpressionParser();
    Expression expression = parser.parseExpression("-9223372036854775808L");
    assertThat(expression.getValue(context, Long.class)).isEqualTo(Long.MIN_VALUE);
}

The above test fails with:

org.springframework.expression.spel.SpelParseException: EL1036E: The value '9223372036854775808' cannot be parsed as a long

Comment From: sbrannen

After further investigation, I have determined that the following expressions can be used as workarounds, depending on your exact use case.

Integer.MIN_VALUE (-2147483648)

  • -2147483647 - 1 -- always works (equal to Integer.MIN_VALUE + 1 - 1)
  • -2147483648L -- always works (stores Integer.MIN_VALUE in a Long)
  • 0.MIN_VALUE -- always works, even with SimpleEvaluationContext; 0 can be any integer literal
  • T(Integer).MIN_VALUE -- requires StandardEvaluationContext
  • T(Integer).valueOf('-2147483648') -- requires StandardEvaluationContext

Long.MIN_VALUE (-9223372036854775808)

  • -9223372036854775807 - 1 -- always works (equal to Long.MIN_VALUE + 1 - 1)
  • 0L.MIN_VALUE -- always works, even with SimpleEvaluationContext; 0 can be any long literal
  • T(Long).MIN_VALUE -- requires StandardEvaluationContext
  • T(Long).valueOf('-9223372036854775808') -- requires StandardEvaluationContext

Comment From: sbrannen

After considerable analysis, I have determined that supporting Integer.MIN_VALUE and Long.MIN_VALUE as integer and long literals is out of scope for the Spring Expression Language (SpEL).

The reason is that the internals of the SpEL tokenizer, parser, and AST node implementations rely heavily on the fact that literals are always positive values. For example, when you specify an integer literal such as -2 in an expression, the value is never stored or represented as -2. Instead, the value is stored as 2 in an IntLiteral which is supplied as the sole operand to the OpMinus AST node. So -2 is effectively represented internally as OpMinus(IntLiteral(2)) and calculated on-the-fly as (0 - 2).

Changing the semantics to actually store -2 in an IntLiteral or LongLiteral would break various constructs in SpEL.

In light of that and the aforementioned workarounds, I am repurposing this as a documentation issue to point out this limitation.

Comment From: sbrannen

@firefoxpdm, are you perhaps the same firefoxpdm that created this issue on JIRA?

Comment From: sbrannen

While working on #32187, I realized that the easiest way to express Integer.MIN_VALUE within an expression (when not evaluated in a StandardEvaluationContext) is to simply calculate the valuate using SpEL's exponential power operator (-2^31).