Español
English

MagmaLib Documentation v2.1

The professional solution for migrating Paper plugins to Folia with a modern API, type-safe composition and maximum performance.

New in v2.1

Type-safe composition: thenApply(), thenAccept(), exceptionally()Context propagation: named() for debugging • 100% backward compatible with v1.x

Installation

Available Now

MagmaLib v2.1 is now available on JitPack. Add the dependency to your project and start building Folia-ready plugins today!

Requirements

Paper 1.20+ or Folia 1.20+ (recommended). Java 17 or higher.

Maven


            <repositories>
                <repository>
                    <id>jitpack.io</id>
                    <url>https://jitpack.io</url>
                </repository>
            </repositories>

            <dependencies>
                <dependency>
                    <groupId>com.github.MagmaEnginers</groupId>
                    <artifactId>MagmaLib</artifactId>
                    <version>2.1.0</version>
                    <scope>provided</scope>
                </dependency>
            </dependencies>

Gradle (Kotlin DSL)

repositories {
                maven("https://jitpack.io")
            }

            dependencies {
                compileOnly("com.github.MagmaEnginers:MagmaLib:2.1.0")
            }

Gradle (Groovy)

repositories {
                maven { url 'https://jitpack.io' }
            }

            dependencies {
                compileOnly 'com.github.MagmaEnginers:MagmaLib:2.1.0'
            }
Authentication Note

JitPack may require authentication for rate limiting. If you encounter issues, add credentials to your ~/.m2/settings.xml or gradle.properties. See FAQ for details.

📁 Alternative: Copy Source

If you prefer not to use external dependencies:

  1. Download MagmaLib.java
  2. Copy it to your project: src/main/java/your/package/MagmaLib.java
  3. Done! No external dependencies required.

Quick Start

Integrate MagmaLib in 3 simple steps:

  1. Add the dependency or copy MagmaLib.java into your project
  2. Initialize in onEnable() with MagmaLib.init(this)
  3. Replace Bukkit.getScheduler() with MagmaLib.task()
// In your main JavaPlugin
@Override
public void onEnable() {
    // 1. Initialize MagmaLib
    MagmaLib.init(this);

    // 2. Use Task Builder (standard API - safe)
    MagmaLib.task(() -> {
        player.sendMessage("Hello from MagmaLib!");
    })
    .at(player.getLocation())
    .afterTicks(20)
    .handleException(e -> getLogger().warning("Error: " + e.getMessage()))
    .run();

    // 3. Or type-safe composition (new in v2.1)
    MagmaLib.<Integer>task(() -> calculateScore(player))
        .thenApply(score -> score > 100 ? "Excellent!" : "Keep trying")
        .thenAccept(msg -> player.sendMessage(msg))
        .run();
}

Initialization

init() Standard

void init(Plugin plugin)

Initializes the library. Call in onEnable(). Throws IllegalStateException if plugin is null.

@Override
public void onEnable() {
    MagmaLib.init(this);
}

isFolia()

boolean isFolia()

Automatically detects if the server is Folia using cached reflection. Throws exception if init() was not called.

if (MagmaLib.isFolia()) {
    // Code optimized for Folia
} else {
    // Fallback for Paper/Spigot
}

Task Builder (Standard API)

Safe and readable API for most use cases. Includes automatic validations and error handling. 100% compatible with v1.x.

Backward Compatible

All your MagmaLib v1.x code works without changes in v2.1. The new composition features are optional.

task(Runnable) v1.x

TaskBuilder task(Runnable runnable)

Entry point for tasks without return value. Compatible with previous versions.

task(Supplier) v2.1

<T> TaskBuilder<T> task(Supplier<T> supplier)

Entry point for type-safe composition with thenApply/thenAccept.

at()

TaskBuilder<T> at(Location location)

Executes in the chunk region (Folia) or main thread (Paper).

with()

TaskBuilder<T> with(Entity entity)

Binds the task to the entity's scheduler in Folia.

afterTicks()

TaskBuilder<T> afterTicks(long ticks)

Direct delay in Minecraft ticks (1 tick = 50ms).

everyTicks()

TaskBuilder<T> everyTicks(long ticks)

Repetitive interval in ticks for periodic tasks.

handleException()

TaskBuilder<T> handleException(Consumer<Exception>)

Custom error handler for the task.

named() v2.1

TaskBuilder<T> named(String name)

Assigns a name for debugging and logs with rich context.

unsafe() Direct

TaskBuilder<T> unsafe()

⚠️ Disables validations. Only in hot paths with guaranteed parameters.

run()

Task run()

Executes the task and returns a Task object for manual cancellation.

Important

By default, .at() and .with() validate that the chunk is loaded and the entity is valid. Use .cancelIfUnloaded(false) to disable.

Type-Safe Composition v2.1

New functional API style like CompletableFuture with complete type-safety. Transform, consume and handle errors declaratively.

Why use composition?
  • ✅ Type-safety at every pipeline step (no casts)
  • ✅ More readable and maintainable code
  • ✅ Integrated error handling
  • ✅ Thread-aware: executes on correct scheduler automatically

Composition Flow

task(() -> calculate()) thenApply(x -> transform) thenAccept(x -> consume) exceptionally(e -> fallback) run()

thenApply() Transform

<R> TaskBuilder<R> thenApply(Function<T, R> mapper)

Transforms the result by applying a function. Changes the builder type for type-safe chaining.

// Integer → String
MagmaLib.<Integer>task(() -> player.getLevel())
    .thenApply(level -> "Level: " + level)
    .thenAccept(msg -> player.sendMessage(msg))
    .run();

thenAccept() Consume

TaskBuilder<T> thenAccept(Consumer<T> action)

Consumes the result without returning a value. Ideal for side effects like sending messages.

// Consume final result
MagmaLib.task(() -> fetchUserData(player))
    .thenApply(data -> data.username)
    .thenAccept(username -> {
        player.sendMessage("Welcome, " + username);
    })
    .run();

exceptionally() Fallback

TaskBuilder<T> exceptionally(Function<Throwable, T> fallback)

Provides a recovery value when an exception occurs. Similar to try-catch but functional.

// Fallback on error
MagmaLib.task(() -> database.query(player.getUniqueId()))
    .thenApply(result -> result.score)
    .exceptionally(e -> {
        getLogger().warning("Query failed: " + e.getMessage());
        return 0; // Default value
    })
    .thenAccept(score -> updateUI(score))
    .run();

named() Debugging

TaskBuilder<T> named(String name)

Assigns a name for logs with context. Includes task name, location and entity in error messages.

// More useful logs in production
MagmaLib.task(() -> processTransaction(player, amount))
    .named("EconomyDeduction")
    .at(player.getLocation())
    .handleException(e -> {
        // Log: "Error in MagmaLib task ['EconomyDeduction'] location=world@123,45,67..."
        getLogger().severe("Transaction failed: " + e.getMessage());
    })
    .run();

Complete Example: Data Pipeline

// Complete type-safe pipeline
MagmaLib.<List<ItemStack>>task(() -> player.getInventory().getContents())
    .named("InventoryProcessor")
    .at(player.getLocation())

    // Filter non-null items (List<ItemStack> → List<ItemStack>)
    .thenApply(items -> items.stream()
        .filter(Objects::nonNull)
        .toList())

    // Count items (List<ItemStack> → Integer)
    .thenApply(items -> items.size())

    // Format message (Integer → String)
    .thenApply(count -> "You have " + count + " items")

    // Send message to player (side effect)
    .thenAccept(message -> player.sendMessage(message))

    // Handle errors with fallback
    .exceptionally(e -> {
        getLogger().warning("Error processing inventory: " + e.getMessage());
        return "Error loading items";
    })

    // Execute with automatic routing for Folia
    .run();
Note about thenApply

thenApply() requires using MagmaLib.<T>task(Supplier<T>). If you use task(Runnable), it will throw IllegalStateException. The methods thenAccept() and exceptionally() also require Supplier mode.

Direct API (High Performance)

Optimized methods for hot paths. No builder, no validations, maximum throughput.

Security Warning

These methods omit validations. Use them ONLY when you manually guarantee: location.getWorld() != null, chunk loaded, and entity.isValid().

runDirect()

void runDirect(Runnable runnable)

Immediate execution on global thread. ~25% faster than task().run().

runDirectAt()

void runDirectAt(Location, Runnable)

Execution in chunk region. Ideal for massive block processing.

runDirectWith()

void runDirectWith(Entity, Runnable)

Execution on entity scheduler. For massive update loops. v2.1 Fix: 0L delay for immediate execution.

runDirectLater()

void runDirectLater(Runnable, long delayTicks)

Direct delay in ticks. No TimeUnit conversion.

runDirectTimer()

void runDirectTimer(Runnable, long periodTicks)

Direct periodic task. No builder or extra allocations.

runTimerUntilFast() New

Task runTimerUntilFast(Runnable, long, BooleanSupplier)

Optimized version without AtomicReference. ~15% faster.

Async & CompletableFuture

runAsync()

CompletableFuture<Void> runAsync(Runnable)

Executes asynchronously and returns a future for composition.

MagmaLib.runAsync(() -> {
    return heavyComputation();
}).thenAccept(result -> {
    MagmaLib.runSync(() -> player.sendMessage(result));
});

callSync()

<T> CompletableFuture<T> callSync(Supplier<T>)

Executes on main thread and returns value via CompletableFuture.

MagmaLib.callSync(() -> {
    return player.getInventory().getContents();
}).thenAccept(items -> {
    // Process on async thread
});

Advanced Utilities

runWithRetry() New

void runWithRetry(Runnable, int, long, TimeUnit)

Executes with automatic retries. Compatible with Folia.

runTimerUntil() New

Task runTimerUntil(Runnable, long, BooleanSupplier)

Executes periodically until a condition is met.

forAllPlayers()

void forAllPlayers(Consumer<Player>)

Iterates players with safe error handling. Executes on main thread.

forAllLoadedChunks()

void forAllLoadedChunks(Consumer<Chunk>)

Processes loaded chunks (executes async by default).

ticksToMs() / msToTicks()

long ticksToMs(long) | long msToTicks(long)

Optimized time conversion (multiplication by 50L).

executeIfLoaded()

void executeIfLoaded(Location, Runnable)

Executes only if the location's chunk is loaded.

Entity Handling

Best Practice

Always use .with(entity) for entity operations in Folia. Guarantees execution on correct scheduler and prevents race conditions.

Example: Safe Update

MagmaLib.task(() -> {
    if (!entity.isValid() || entity.isDead()) return;

    // Safe operations with entity
    entity.setCustomName("§aProcessed");
    entity.setGlowing(true);
})
.with(entity)  //  Correct routing in Folia
.cancelIf(() -> !entity.isValid())  //  Automatic cancellation
.handleException(e -> getLogger().warning("Error: " + e.getMessage()))
.run();

High Performance Mode (⚠️)

// Only if you guarantee entity.isValid() == true
for (Entity entity : entities) {
    MagmaLib.task(() -> {
        entity.setCustomName("Updated");
    })
    .with(entity)
    .unsafe()  // ⚠️ Omits isValid() check
    .run();
}

Compatibility

FeaturePaper/SpigotFolia
task().run()✅ Bukkit Scheduler✅ GlobalRegionScheduler
task().at(location)✅ Main Thread✅ RegionScheduler
task().with(entity)✅ Main Thread✅ EntityScheduler
task().async()✅ Async Scheduler✅ AsyncScheduler
thenApply/thenAccept v2.1✅ Functional✅ Functional
runDirectAt()✅ Main Thread✅ RegionScheduler
runDirectWith()✅ Main Thread✅ EntityScheduler
cancelIfUnloaded✅ Verification✅ Verification
handleException✅ Try-catch✅ Try-catch
named() v2.1✅ Context logs✅ Context logs

Note: MagmaLib automatically detects the server type and uses the appropriate scheduler. Your code works on both without modifications.

Performance Comparison

Benchmark: 10,000 iterations of simple tasks on test server (Paper 1.21.4, Java 21).

ScenarioFoliaLibMagmaLib v1.2MagmaLib v2.1Improvement
Simple global task1.2 ms1.0 ms0.9 ms+25%
Regional task1.5 ms1.2 ms1.1 ms+27%
Task with compositionN/A2.8 ms*2.1 ms+25%
Loop with validations3.8 ms3.2 ms2.1 ms+47%
Hot path (runDirect*)1.3 ms0.9 ms0.8 ms+38%

*v1.2 with manual CompletableFuture; v2.1 with integrated native composition

Conclusion: MagmaLib v2.1 is ~25-47% faster than alternatives in hot paths, with integrated type-safe composition and zero additional overhead.

Frequently Asked Questions

Will my v1.x code work in v2.1?

Yes, 100% compatible. All v1.x APIs work without changes. The new composition features are optional and are activated by using task(Supplier<T>) instead of task(Runnable).

When to use thenApply vs thenAccept?

thenApply: When you need to transform the value and continue the pipeline (returns new type). thenAccept: When you want to consume the final value for a side effect (doesn't return value, ends the pipeline).

Is it safe to use .unsafe()?

Yes, if you follow these rules:

  • ✅ Validate manually before calling
  • ✅ Use only in code you fully control
  • ✅ Document why it's safe in that context
  • ❌ Never use with user inputs or external data

Does it work on Paper and Folia?

✅ Yes, 100% compatible. MagmaLib automatically detects the server type and uses the appropriate scheduler:

  • Folia: RegionScheduler, EntityScheduler, GlobalRegionScheduler
  • Paper/Spigot: Traditional Bukkit Scheduler

Your code needs no changes to work on both.

How to debug tasks with named()?

Error logs will automatically include:

  • 🏷️ Task name: ['MyTask']
  • 📍 Location: location=world@x,y,z
  • 👤 Entity: entity=Player[Notch]
  • 📋 Creation stack trace (if you enable debug)

How to install v2.1 now?

While JitPack is not available:

  1. Download MagmaLib.java from the repository
  2. Copy it to your plugin: src/main/java/your/package/MagmaLib.java
  3. Done! No external dependencies

Coming soon: jitpack.io/#MagmaEnginers/MagmaLib

Do I need authentication for JitPack?

JitPack may require authentication to avoid rate limiting. Follow these steps:

Maven: Add to ~/.m2/settings.xml:

<servers>
                <server>
                    <id>jitpack.io</id>
                    <username>YOUR_GITHUB_USERNAME</username>
                    <password>ghp_YOUR_TOKEN</password>
                </server>
                </servers>

Gradle: Add to ~/.gradle/gradle.properties:

gpr.user=YOUR_GITHUB_USERNAME
                gpr.key=ghp_YOUR_TOKEN

Create a token at GitHub Settings with scope read:packages.