mybatis/mybatis-3 · error · RuntimeSqlException

Line missing end-of-line terminator ({}) => {}

Error message

Line missing end-of-line terminator ({}) => {}

What it means

ScriptRunner throws RuntimeSqlException from checkForMissingLineTerminator() when, after consuming the whole script, leftover non-whitespace text remains in the command buffer. This means the final SQL command was never terminated with the configured delimiter (default ';'), so it was never executed. The offending partial command text is included in the message.

Source

Thrown at src/main/java/org/apache/ibatis/jdbc/ScriptRunner.java:209

      }
    } catch (Throwable t) {
      throw new RuntimeSqlException("Could not commit transaction. Cause: " + t, t);
    }
  }

  private void rollbackConnection() {
    try {
      if (!connection.getAutoCommit()) {
        connection.rollback();
      }
    } catch (Throwable t) {
      // ignore
    }
  }

  private void checkForMissingLineTerminator(StringBuilder command) {
    if (command != null && command.toString().trim().length() > 0) {
      throw new RuntimeSqlException("Line missing end-of-line terminator (" + delimiter + ") => " + command);
    }
  }

  private void handleLine(StringBuilder command, String line) throws SQLException {
    String trimmedLine = line.trim();
    if (lineIsComment(trimmedLine)) {
      Matcher matcher = DELIMITER_PATTERN.matcher(trimmedLine);
      if (matcher.find()) {
        delimiter = matcher.group(5);
      }
      println(trimmedLine);
    } else if (commandReadyToExecute(trimmedLine)) {
      command.append(line, 0, line.lastIndexOf(delimiter));
      command.append(LINE_SEPARATOR);
      println(command);
      executeStatement(command.toString());
      command.setLength(0);
    } else if (trimmedLine.length() > 0) {

View on GitHub (pinned to 008069adb1)

Solutions

  1. Add the delimiter (';' by default) at the end of the last statement in the script.
  2. If the script uses a different terminator, ensure the final statement ends with that exact delimiter string.
  3. If you deliberately want the whole script sent at once, call runner.setSendFullScript(true) so line-by-line delimiter parsing is bypassed.

Example fix

-- before (end of script.sql)
INSERT INTO users(id, name) VALUES (3, 'carl')

-- after
INSERT INTO users(id, name) VALUES (3, 'carl');
Defensive patterns

Strategy: validation

Validate before calling

// Before running, check the last non-whitespace chars of the script end with the delimiter
String sql = Files.readString(path);
String trimmed = sql.trim();
String delim = ";"; // match ScriptRunner delimiter
if (!trimmed.endsWith(delim)) {
  throw new IllegalArgumentException("Script must end with '" + delim + "': " + path);
}

Try / catch

try {
  runner.runScript(reader);
} catch (RuntimeSqlException e) {
  if (e.getMessage() != null && e.getMessage().contains("missing end-of-line terminator")) {
    // append delimiter and retry once with corrected script
  }
  throw e;
}

Prevention

When it happens

Trigger: A .sql script whose last statement lacks a trailing ';' (e.g. ends with the last line of an INSERT with no terminator), or a custom delimiter (setDelimiter) that does not match what the script uses on its final statement; also sendFullScript=false with a last line that has no delimiter.

Common situations: Hand-edited migration files where the final semicolon was accidentally deleted; scripts generated by tools that omit the last terminator; changing the delimiter for stored procedures (DELIMITER $$) but forgetting the final $$.

Related errors


AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14). Data as JSON: /api/errors/c7ea44792c6e2d9b. Report an issue: GitHub.