Skip to content

Error handling

Compiles are asynchronous, so nothing is thrown at you directly: compileString and compilePath never throw synchronously. Every failure — an engine error, a Sass error, a timeout, a cancellation, a closed compiler — arrives as a failed future.

try {
  var output = compiler.compileString(scss, options).get(30, TimeUnit.SECONDS);
  return output.getCss();
} catch (ExecutionException e) {
  if (e.getCause() instanceof JsassCompilationException sassError) {
    // a real Sass problem: report it to the developer
    return "/* " + sassError.getMessage() + " */";
  }
  throw e;
}

The exception hierarchy

Exception
└── JsassException                         checked, the root of everything jsass reports
    ├── JsassCompilationException          a compile failed
    │   ├── JsassCompilationTimeoutException      exceeded its timeout
    │   ├── JsassCompilationInterruptedException  worker thread was interrupted
    │   ├── SassFunctionExecutionException        a custom function threw
    │   └── SassImporterExecutionException        an importer threw
    └── AmbiguousImportException           one import matched several stylesheets

RuntimeException
└── SassPathTraversalException             an import escaped its allowed root

Reading a Sass error

JsassCompilationException keeps the diagnostics dart-sass produced in structured form:

Getter Content
getSassMessage() dart-sass's own message, unmodified
getSourceSpan() a SourceSpan record: URL plus 0-indexed start/end line and column
getSassStack() the Sass-level stack trace, when there is one
getRawV8Stack() the JavaScript stack — for debugging jsass itself

getMessage() renders a sanitized summary: the message, the last path segment of the URL and a 1-indexed line and column. It never contains an absolute filesystem path or V8 internals, which makes it safe to put in an HTTP response or a log that leaves the machine. When you want more, read the structured getters.

var span = e.getSourceSpan();
if (span != null) {
  log.error("{} at {}:{}:{}", e.getSassMessage(), span.url(),
      span.startLine() + 1, span.startColumn() + 1);
}

Timeouts and interrupts

Both are compilation failures, but they mean different things:

  • JsassCompilationTimeoutException — the compile ran longer than its effective timeout. getElapsed() returns the Duration that passed before the engine was terminated.
  • JsassCompilationInterruptedException — something interrupted the worker thread from the outside, typically an executor.shutdownNow() during application shutdown.

Neither carries a source span: there is no line of SCSS to blame.

Cancelling the future (future.cancel(true)) asks the engine to abort the running compile, and the future completes as cancelled rather than with one of the exceptions above.

Warnings and debug messages

@warn and @debug are not errors and never fail a compile. They reach you through a SassLogger on the options:

var options = StringOptions.builder()
    .logger(new SassLogger() {
      @Override
      public void warn(String message, Options opts) {
        log.warn("{}{}", opts.isDeprecation() ? "[deprecated] " : "", message);
      }

      @Override
      public void debug(String message, Options opts) {
        log.debug(message);
      }
    })
    .build();

The Options argument adds context: getSpan() locates the message in the source, isDeprecation() says whether it is a deprecation warning, and getStack() gives the Sass call stack when one is available. Without a logger, dart-sass's default handling applies.

If deprecation warnings are the problem rather than the signal, silenceDeprecations, quietDeps and fatalDeprecations on the options are the sharper tools.

Engine-level logging

JavetSlf4JLogger on the compiler builder is a different channel: it reports what the JavaScript engine itself is doing, not what your stylesheets say. Attach it when you are debugging module resolution or an engine failure, not for stylesheet diagnostics.