# CoffeeCatch turns a POSIX signal into something you can catch

> CoffeeCatch is a small C library that lets you guard a block of native code with try and catch macros, so a segmentation fault in JNI code becomes a Java exception instead of a process kill, and the caveats are stated as plainly as the feature.

**xroche/coffeecatch** — CoffeeCatch, a tiny native POSIX signal catcher (especially useful for JNI code on Android/Dalvik)

- Repository: https://github.com/xroche/coffeecatch
- Stars: 695 · Forks: 137
- Language: C
- License: BSD-2-Clause
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/xroche-coffeecatch

## What the library does and what it refuses to do

CoffeeCatch is described as a tiny native POSIX signal catcher, especially useful for JNI code on Android or Dalvik, though it can be used in non-Java projects. It allows you to gracefully recover from a signal such as SIGSEGV or SIGBUS as if it was an exception. The limits are stated in the same sentence as the feature, and that is what makes the readme worth reading. It will not gracefully recover from allocator or mutexes corruption, however, but at least most gentle crashes, such as null pointer dereferencing, integer division or stack overflow, should be handled without too much trouble. That is a precise contract. A signal handler runs in a context where almost nothing is safe to call, and this library does the minimum that makes recovery possible while being explicit about the class of failure it cannot touch.

## The macros, and the shape of a protected block

The C interface is three macros that read like a language extension.

```c
/** Enter protected section. **/
COFFEE_TRY() {
  /** Try to call 'call_some_native_function'. **/
  call_some_protected_function();
} COFFEE_CATCH() {
  /** Caught a signal: throw Java exception. **/
  /** In pure C projects, you may print an error message (coffeecatch_get_message()). **/
  coffeecatch_throw_exception(env);
} COFFEE_END();
```

Outside of JNI the same three macros work, and the difference is what you do in the catch block, which is where you read the message with coffeecatch_get_message and print it.

```c
void my_function() {
  COFFEE_TRY() {
    /** Try to call 'call_some_native_function'. **/
    call_some_native_function();
  } COFFEE_CATCH() {
    /** Caught a signal. **/
    const char*const message = coffeecatch_get_message();
    fprintf(stderr, "**FATAL ERROR: %s\n", message);
  } COFFEE_END();
}
```

The catch block is not restricted to printing, so a real project can release a resource, restore state or rethrow. What the readme is explicit about is ownership: the handler is thread-safe, but the client must have exclusive control on the signal handlers, since the library is installing its own signal handlers on top of the existing ones. That is the single most important operational fact in the document, because a process that already handles SIGSEGV itself will have its handler replaced.

## The JNI path, and what the stack trace looks like

Inside JNI the library adds a variant that raises a Java exception from the catch block. The build step is two source files added to the library block of an Android.mk file.

```
  LOCAL_CFLAGS := -funwind-tables -Wl,--no-merge-exidx-entries
```

The flag is not optional if you want useful stack traces, because the unwinding information has to be present in the binary, and on ARM the additional linker switch solves certain issues with unwinding, though the switch is possibly not needed anymore. The C sources to add are the library file and the JNI wrapper file.

```
LOCAL_SRC_FILES += coffeecatch.c coffeejni.c
```

The protected JNI call is written with the JNI macro, and the readme shows the resulting stack trace, where the Java side reports a fatal exception with a cause of java.lang.Error describing signal 11, address not mapped to object, at a given address in the native library, followed by the native frames. The note is that the last exception with the native backtrace is produced on Android 4.1.1 and later, so older releases get the signal description without the native frames.

## The C++ variants unify cleanup without unifying exceptions

The C++ friendly macros are a separate set, and the readme is careful about exactly what they add. A sentinel object runs the cleanup from its destructor, so cleanup happens even if the protected block exits by a return or a propagating C++ exception, both of which are forbidden with the plain COFFEE_TRY macros.

```cpp
void my_function() {
  COFFEE_CXX_TRY() {
    call_some_native_function();
    return;                     // safe: the sentinel still cleans up
  } COFFEE_CXX_CATCH() {
    const char*const message = coffeecatch_get_message();
    fprintf(stderr, "**FATAL ERROR: %s\n", message);
  } COFFEE_CXX_END();
}
```

The distinction the readme insists on is that the C++ variants unify cleanup, not catching. COFFEE_CXX_CATCH catches only signals and never a C++ throw, while a C++ catch catches only C++ exceptions and never a signal. The two mechanisms stay separate and can be nested. A signal inside the protected block goes to the CoffeeCatch catch, while a C++ throw skips it entirely and unwinds, running the sentinel cleanup on the way, to the enclosing C++ catch. That separation is the right design, because the whole point of the C++ variant is that the destructors still run, and a mechanism that swallowed C++ exceptions would make the two error models indistinguishable.

## The build treats warnings as errors

The Makefile is small and its comments explain its choices. The toolchain variables are overridable, with cc, c++, ar and rm as the POSIX defaults, and the comment notes that continuous integration overrides CC and CXX with a matching pair such as gcc and g++ or clang and clang++, and that the C suite must stay on CC so clang coverage is not silently swapped for the default C++ compiler. The flags are split the same way. CFLAGS and CXXFLAGS hold the user-overridable optimisation and debug flags, everything mandatory is appended with override so it survives a command line CFLAGS, and the two are kept apart so that make CFLAGS=-std=gnu99 does not reach the C++ compiler. The mandatory set includes position-independent code, threads, and a strict warning set that includes -W, -Wall, -Wextra and -Werror, with only unused functions exempted.

```
override CFLAGS   += -fPIC -pthread \
                    -W -Wall -Wextra -Werror -Wno-unused-function
```

The shared library wiring is per platform. On Darwin the output is a dylib built with -dynamiclib and an install name of @rpath, and on other systems it is a shared object built with -soname, with -Wl,--no-undefined and -rdynamic, and dl linked in. The JNI wrapper needs the JDK headers, so only the JNI check target builds it, with the include path taken from JAVA_HOME.

## Where this is the wrong choice

Three limits are worth naming. The first is the one the readme leads with, which is that allocator and mutex corruption is out of scope, and it is worth being precise about why. Catching a signal and returning into the same process after a heap corruption means the allocator's internal state is already wrong, so whatever the application does next will operate on damaged data, and the recovery will look like it worked right up until it does not. The second is the signal handler ownership requirement, which is not a limitation of this library so much as a property of POSIX, and it means CoffeeCatch cannot coexist with a runtime that installs its own handlers without careful ordering. The third is the platform dependence of the useful output. The stack traces depend on unwinding tables compiled into your libraries, and on ARM the linker switch may still be needed, which makes adoption a build system change rather than a source change. What the library does deliver is small, focused and honest about its edges, which is the right shape for something that sits underneath a crash handler.

## Conclusion

CoffeeCatch suits a developer with native code called from Java who needs a crash in that code to become an exception rather than a process death, and it suits anyone writing a small C or C++ tool who wants a guarded block without a language runtime that has exceptions. It is the wrong tool if you need to survive a corrupted heap or a broken mutex, since the readme says plainly that it will not gracefully recover from allocator or mutex corruption. It is also the wrong choice if you want stack traces on every platform without rebuilding your libraries, because the unwinding information has to be compiled in. Before using it, read the caveats about signal handler ownership, since the client must have exclusive control over the signal handlers, and check the Makefile flags, because the build treats warnings as errors. The last push was on 2026-10-13 and the repository publishes no releases.

## FAQ

### What does CoffeeCatch do with a segmentation fault?

It lets you recover from a signal such as SIGSEGV or SIGBUS as if it were an exception, by wrapping the risky code in COFFEE_TRY, COFFEE_CATCH and COFFEE_END. The README is explicit that it will not gracefully recover from allocator or mutex corruption, but that most gentle crashes such as null pointer dereferencing, integer division and stack overflow should be handled.

### How do I use CoffeeCatch from JNI code?

Add coffeecatch.c and coffeejni.c to your library sources with LOCAL_SRC_FILES, build your libraries with -funwind-tables and on ARM possibly -Wl,--no-merge-exidx-entries so the stack traces are useful, and protect the call with the COFFEE_TRY_JNI macro, where the catch block raises a Java exception through coffeecatch_throw_exception.

### Does CoffeeCatch catch C++ exceptions?

No. The COFFEE_CXX_TRY and COFFEE_CXX_CATCH macros unify cleanup, not catching, so COFFEE_CXX_CATCH catches only signals and never a C++ throw, and a C++ catch catches only C++ exceptions and never a signal. The two mechanisms stay separate and can be nested, and the C++ variant exists so that cleanup still happens when the protected block exits by a return or a propagating exception.

## Sources

- [Issues](https://github.com/xroche/coffeecatch/issues)
- [License: BSD-2-Clause](https://github.com/xroche/coffeecatch/blob/master/LICENSE)
- [README](https://github.com/xroche/coffeecatch/blob/master/README.md)
- [xroche/coffeecatch on GitHub](https://github.com/xroche/coffeecatch)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/xroche-coffeecatch
