Getting started
Warning
jsass 6 is still in the early stages of development. API breaks are to be expected!
Requirements
- Java 21 or newer. jsass 6 is compiled to Java 21 bytecode and is tested on 21 and 25.
- A JavaScript engine: the Javet native library for your platform — see compatibility.
- A copy of dart-sass: the
sassWebJar (V8) or anode_modules/sassdirectory (Node). jsass never downloads it for you.
1. Add the repository
6.0.0-alpha.1 is not on Maven Central. Pre-releases live in this project's GitLab package registry, which is world-readable — no token, no login.
Stable 5.x releases remain on Maven Central; 6.x will be published there again once the API settles.
2. Pick your three pieces
Every jsass 6 setup answers three questions. The defaults below are the V8 route, which needs no
Node.js installation and no node_modules directory.
| Decision | V8 route | Node route |
|---|---|---|
| Which engine? | jsass.javet.v8.compiler |
jsass.javet.node.compiler |
| Where does Sass live? | jsass.javet.webjar-module-resolver |
jsass.javet.node-modules-resolver |
Who parses package.json? |
jsass.jackson2 or jsass.jackson3 |
jsass.jackson2 or jsass.jackson3 |
The engines page compares the two routes; the JSON SPI page explains why a JSON parser is part of the picture at all.
3. Add the dependencies
dependencies {
implementation("io.bit3:jsass:6.0.0-alpha.1")
implementation("io.bit3:jsass.javet.v8.compiler:6.0.0-alpha.1")
implementation("io.bit3:jsass.javet.webjar-module-resolver:6.0.0-alpha.1")
implementation("io.bit3:jsass.jackson2:6.0.0-alpha.1")
// dart-sass itself, and the native engine for your platform
runtimeOnly("org.webjars.npm:sass:1.86.3")
runtimeOnly("com.caoccao.javet:javet-v8-linux-x86_64:5.0.5")
// any SLF4J provider
runtimeOnly("org.slf4j:slf4j-simple:2.0.17")
}
<dependencies>
<dependency>
<groupId>io.bit3</groupId>
<artifactId>jsass</artifactId>
<version>6.0.0-alpha.1</version>
</dependency>
<dependency>
<groupId>io.bit3</groupId>
<artifactId>jsass.javet.v8.compiler</artifactId>
<version>6.0.0-alpha.1</version>
</dependency>
<dependency>
<groupId>io.bit3</groupId>
<artifactId>jsass.javet.webjar-module-resolver</artifactId>
<version>6.0.0-alpha.1</version>
</dependency>
<dependency>
<groupId>io.bit3</groupId>
<artifactId>jsass.jackson2</artifactId>
<version>6.0.0-alpha.1</version>
</dependency>
<!-- dart-sass itself, and the native engine for your platform -->
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>sass</artifactId>
<version>1.86.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.caoccao.javet</groupId>
<artifactId>javet-v8-linux-x86_64</artifactId>
<version>5.0.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
jsass.javet.v8.compiler pulls in jsass, jsass.javet and com.caoccao.javet:javet
transitively, and the WebJar resolver adds org.webjars:webjars-locator-core. The native engine
artifact is the one you must name yourself — it is platform-specific, so no POM can choose it
for you.
Cross-platform builds
Declare every native you ship for. The artifacts do not conflict; Javet loads the one that matches the running OS and architecture. See compatibility for the list.
4. Compile something
import io.bit3.jsass.StringOptions;
import io.bit3.jsass.javet.v8.JavetV8JsassCompiler;
import io.bit3.jsass.javet.webjars.WebjarV8ModuleResolver;
import io.bit3.jsass.json.jackson2.Jackson2JsonDeserializer;
import java.net.URI;
import java.util.concurrent.TimeUnit;
var moduleResolver = WebjarV8ModuleResolver.builder()
.jsonDeserializer(new Jackson2JsonDeserializer())
.build();
try (var compiler = JavetV8JsassCompiler.builder()
.moduleResolver(moduleResolver)
.build()) {
var options = StringOptions.builder()
.url(URI.create("virtual:root.scss"))
.sourceMap(false)
.build();
var output = compiler
.compileString("$c: #36f; .button { color: $c; }", options)
.get(30, TimeUnit.SECONDS);
System.out.println(output.getCss());
}
Compiling a file from disk is the same call with a Path:
var output = compiler.compilePath(Path.of("src/main/scss/app.scss"), options)
.get(30, TimeUnit.SECONDS);
The compiler is a singleton, not a helper
Create one compiler and keep it for the lifetime of your application — a Spring bean, a static field, whatever fits. It owns an executor, the module resolver and its caches, and (if you configure one) the engine pool that makes repeated compiles cheap.
@Bean
public JsassCompiler jsassCompiler() {
var moduleResolver = WebjarV8ModuleResolver.builder()
.jsonDeserializer(new Jackson2JsonDeserializer())
.build();
return JavetV8JsassCompiler.builder().moduleResolver(moduleResolver).build();
}
A compiler is safe to call from multiple threads: each compile runs on the compiler's executor.
close() stops accepting new compiles, waits for the in-flight ones and releases the
compiler-owned executor. It is idempotent, and after it every compileString / compilePath
returns a future that has already failed with IllegalStateException.
Compiling a lot?
Out of the box every compile gets a fresh JavaScript runtime, which means dart-sass is evaluated again each time. An engine pool reuses runtimes — and with them the already-compiled Sass modules — and is the single biggest win for a service that compiles continuously.
Next steps
- Options — source maps, load paths, output style, deprecation handling.
- Custom functions — call back into Java from SCSS.
- Importers — serve stylesheets from a database, a WebJar, or thin air.
- Error handling — what a failed future carries.
- Examples — runnable projects in the repository.