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
└── AmbiguousImportRuntimeException unchecked carrier of an AmbiguousImportException
AmbiguousImportRuntimeException exists because the Importer SPI declares no checked
exceptions: an importer that detects an ambiguous import (WebjarImporter does) throws it, and
getCheckedCause() returns the typed AmbiguousImportException with every candidate.
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 |
getRawEngineStack() |
the raw stack trace reported by the JavaScript engine (V8, Node or GraalJS) — for debugging jsass itself; on the embedded engine, dart-sass's own formatted error with the source excerpt |
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 engine 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 theDurationthat passed before the engine was terminated.JsassCompilationInterruptedException— something interrupted the worker thread from the outside, typically anexecutor.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.
The embedded engine cannot abort a single compile inside dart-sass. On a timeout or a cancel it fails the future at once and retires the dart-sass process, which takes no new compiles and ends once its other compiles are done; see one process per compiler.
When the embedded engine cannot start dart-sass at all — no executable found, a path that does
not exist, a dart-sass too old for protocol 3 — the compile fails with a
JsassCompilationException whose message names the cause: the sources it tried, the missing path,
or the protocol version it found and the ones it supports.
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.
Module resolution failures
On the JavaScript engines, when the sass module — or postcss / autoprefixer with autoprefixer(true)
— cannot be resolved or loaded, the compile fails with a JsassCompilationException that names
the specifier and says how to make it resolvable, instead of a generic engine error. The same
happens when postcss or autoprefixer resolve to CommonJS rather than an ES module.
Engine-level logging
On the Javet engines, Javet's own diagnostics go to SLF4J by default — no setup needed; replace the bridge with
JavetOptions.javetLogger on the compiler builder. This is a different
channel from the SassLogger: it reports what the JavaScript engine itself is doing, not what your
stylesheets say. Turn it up when you are debugging module resolution or an engine failure, not for
stylesheet diagnostics. jsass's own classes log under io.bit3.jsass.