What is Hackers' Pub?

Hackers' Pub is a place for software engineers to share their knowledge and experience with each other. It's also an ActivityPub-enabled social network, so you can follow your favorite hackers in the fediverse and get their latest posts in your feed.

0

집회에 나간다는 건 굉장히 내가 할 수 있는 최고의 어떤 정치적 행위를 하는 부분이고, 집회를 못 오시더라도 저희가 생중계도 하기 때문에 유튜브로 같이 보고, 그것도 나름의 참여 방식이거든요. 응원봉 물결을 보고 있으면 뭔가 좀 장엄한 게 있거든요. 나라가 어려울 때마다 이렇게 자기 일상을 잠시 뒤로 미뤄두고 이렇게 거리로 나오시는 그 마음들이 엄청나게 많이 모이는 거잖아요. 우리 사회는 여전히 건강하게, 지속 가능하게 만들어갈 우리의 힘이 여전히 살아있다 이런 걸 느끼게 해주는 그런 부분들인 것 같아서

0

할 수 있는 게 집회밖에 없다는 당신에게 "할 수 있는 게 집회밖에 없다는 건, 어쩄든 할 수 있는 게 집회가 있는 겁니다. 아무것도 하지 않으면 아무것도 안 일어난다고 생각을 하거든요. 집회가 굉장히 큽니다. 지금도 많은 분들이 모이면 힘이 된다고 생각하거든요. 그 힘으로 탄핵도 가결을 시켰고요. 계엄을 막아낸 것도 사실은 거리로 나온, 국회 정문 앞에 쏟아져나온 시민들이 막아낸 겁니다. 광장에서 목소리를 보태야지 검찰이나 경찰이나 권력기관들이 움직일 수 있는 부분이라고 생각을 해서,

0

좀 더 많은 시민 분들과 함께 공감도 하고, 그런 것들을 어떻게 하면 만들 수 있을까 고민하다가 응원봉 분들이 등장을 했으니까 사회자분들도 가급적이면 2030 여성이었으면 좋겠고 그런 분들이 같이 기획을 해야지 대중적이고 2030들의 정서에도 맞게 집회를 준비해 볼 수 있겠다 싶어가지고 그렇게 좀 했던 것 같아요."

0

일주일이 지난 12월 14일 탄핵 촉구 집회에는 200만명(주최 측 추산)이 모였다. 국회는 이날 윤석열 대통령 탄핵소추안을 가결했다. "시민분들께서 진짜 현명하다고 생각을 해요. 그때 막 선결제 문화도 있고 자기 위치에서 하실 수 있는 모든 것들을 너무 기발한 방식으로 해주시거든요. 응원봉은 사실 전혀 예상을 못 했죠. 집회라고 하면 촛불집회, 촛불도 많이 준비해야겠다 이렇게 생각했는데 집회라는 게 사실은 어떻게 보면 나랑은 상관없는, 이렇게 생각이 드는 부분도 있을 수 있으니까 우리가 어떻게 하면

0

이렇게 써주시면 바로 캐치해가지고 저희가 그 노래를 틀어드리고, 분노스러운 날이었지만 집회가 다이내믹하게, 되게 재밌게 흘러갔어요. 또 시민분들께서 그 이유를 만들어주셔가지고 그때 딱 들었던 게, 절대 우리는 이 싸움에서 질 수가 없겠다, 우리는 반드시 이기겠다, 이런 생각이 딱 들었던 것 같아요."

0

12.3 비상계엄 후 첫 주말 집회에는 100만명(주최 측 추산)이 모였다. "첫 주 12월 7일 날 여의도 집회가 사실 가장 기억에 남는 것 같아요. 탄핵이 표결이 불성립이 돼가지고 이 집회를 그렇다고 종료시키고 해산시킬 수도 없는 부분이고, 박민주 국장님을 사회자로 올려서 이제 〈위플래쉬〉라든지 이런 노래를 했는데 분노랑 다르게 흥겹게만 가면 어떡하지 걱정을 했는데, 오히려 그런 것들을 시민분들께서 잘 승화를 시켜주셔가지고 너무 기발했어요. 자기 핸드폰으로 글자 띄울 수 있잖아요. 내가 누구누구 팬인데 이 노래 꼭 틀어줘,

0

"어제 끝나고 농성장 떠난 시각이 (밤) 11시 반 정도 됐나? 그랬다가 무대를 새벽 4시에 쌓아야 돼가지고 새벽 4시에 나오고. 요즘에 그런 생각이 많이 들어요. S.E.S.의 〈달리기〉라는 노래 가사가 있는데, 지겨운가요 힘겨운가요, 분명한 건 끝이 있을 테니까 끝까지 한번 해봐야겠다, 이런 생각으로 임하는 것 같습니다."

0

"(주머니에서 휴대폰을 꺼내며) 어, 잠시만요. 여보세요? 네네, 알겠습니다. (다시 넣으며) 정말 수십 통 이상 받는 것 같아요. 사무국장이 일이 다 전화로 요청드리고 주문하고, 몸이 하나라서 (웃음) 전화를 할 수밖에 없어서 그런 것 같아요. (다시 꺼내며) 또 전화가 오네요. 여보세요? 네."

0
0

"비상행동으로 보면 (집회를) 12월 7일부터 해가지고 3월 15일 오늘(인터뷰 당시)이죠? 범시민 대행진을 15차례 했고요, 중간중간에 한남동 가서 체포, 구속 촉구하는 그런 집회들을 개최한다든지 이런 것들을 다 고려하면 거의 뭐 한 4, 50회 집회를 하고 있는 것 같아요."

0

"안녕하세요, 비상행동의 사무국장 맡고 있는 심규협이라고 합니다." 비상행동 후원계좌의 '그 이름' [전광판의 '심규협' 글자를 동그라미로 강조] "뭐 심규협은, 심판 규탄 협회의 줄임말, 뭐 이런 우스갯소리로, (이름이) '협'으로 끝나니까 시민분들이 보셨을 때는 약간 협회 줄임말인가? 이렇게 생각하실 수도 있겠다. 해프닝이라고 생각을 합니다. (웃음)"

0

본문으로 안 나와서 내가 직접 옮겨적는 인터뷰 내용 (인트로) "응원봉 물결을 보고 있으면 뭔가 좀 장엄한 게 있거든요." "거의 뭐 한 4, 50회 집회를 하고 있는 것 같아요." "지칠 수도 있다고 생각은 하는데..." "인디언 기우제라는 말이 있잖아요. 포기하지 않으면 저희가 이기는 거라고 생각하기 때문에..."

0

속초여행갔다가 호텔캔슬당하고 오열한 일본인 유튜버

bbs.ruliweb.com/community/boar

얼마전 일본인 유튜버가 부대찌개 먹으려다 1인은 안된다고 한거랑
이번에 비싸게 받으려고 호텔측이 예약취소 해버린거랑...

한국 관광이 왜 잘 안되는지를 확실히 보여주는구만.

0
0
0

Diese Podcastfolge vereint so vieles, weshalb @riffreporter mein journalistisches Lieblingsprojekt ist:

Drei meiner Kollegïnnen nehmen ihre Recherchen und gehen damit unter die Leute, in sehr offenen Debatten, in denen alle Position beziehen können. Und zwar so, dass alle einander zuhören und das trotz sehr diverser Ansichten.

Das Format heißt Unterhausdebatte und ich habe eine davon im eingefangen:

➡️ riffreporter.de/de/gesellschaf

Hört mal rein und sagt, was ihr denkt. ☺️ 🎧

0
0
0
0
0
0
0
0
0
0

Hey @simonSimon Willison — You’re my Severance guide. Lucky you! 🥳

We watched S1 on your glowing recommendation. It was great, but I was quite grumpy about the zero closure. It reminded me of Lost S1. (That boat!)

For S2, no spoilers but, on a 1-5, how would you grade the closure at the end? (I’d give S1 a 1 here.) Trying to set expectations before watching it. Thanks! ☺️

0

Why are Germans being detained by US immigration?

Three tourists and a permanent US resident say they were subjected to aggressive interrogation and held for weeks without knowing why. In response, the German government has extended a travel advisory to its citizens.
dw.com/en/why-are-germans-bein

0
0
0
0

Sobriety

I'd just like to announce, rather randomly, that this is the 17th day since I last drank anything alcoholic. Because I'm feeling proud of myself and a bit grateful.

I did 15 days in Dry January. This one's for Lent but... it feels like it might be long term. I bloody hope so. I've been trying to stop for so long I've forgotten when it was I realised I should try.

Anyway, go me, send prayers, vibes, blessings and manifestations if you're into that kind of thing please.

0

속초여행갔다가 호텔캔슬당하고 오열한 일본인 유튜버

bbs.ruliweb.com/community/boar

얼마전 일본인 유튜버가 부대찌개 먹으려다 1인은 안된다고 한거랑
이번에 비싸게 받으려고 호텔측이 예약취소 해버린거랑...

한국 관광이 왜 잘 안되는지를 확실히 보여주는구만.

0
0

“英ロンドンのサディク・カーン市長がCNNの単独インタビューに応じ、米国人の間で英国の市民権申請ラッシュが起きているのは、ドナルド・トランプ氏がホワイトハウスに返り咲いたことと明らかに関係があるとの見解を示した”

実際にこういうアクションを取る人はごくごく一部で、国を出たいと漠然と思ってる人達はとてつもない数いると思う。

cnn.co.jp/world/35230767.html

0
0
0
0
0
0

Gut! Weitere Stadt in Deutschland vermeidet Gefängnisstrafen - Leipzig will Schwarzfahrer nicht mehr anzeigen

Tausende Menschen werden jährlich wegen Fahrens ohne Ticket inhaftiert

"Es sind fast eine Million offene Fälle in Deutschland. Die Strafverfolgung kommt nicht hinterher, und dann wollen wir Leute ins Gefängnis stecken wegen so einer Lappalie? Das ist nicht verhältnismäßig."

mdr.de/nachrichten/sachsen/lei

0

Gut! Weitere Stadt in Deutschland vermeidet Gefängnisstrafen - Leipzig will Schwarzfahrer nicht mehr anzeigen

Tausende Menschen werden jährlich wegen Fahrens ohne Ticket inhaftiert

"Es sind fast eine Million offene Fälle in Deutschland. Die Strafverfolgung kommt nicht hinterher, und dann wollen wir Leute ins Gefängnis stecken wegen so einer Lappalie? Das ist nicht verhältnismäßig."

mdr.de/nachrichten/sachsen/lei

0

イギリスはアメリカに対しTravel Warningを出したのか。ルールを守ることと、どんな難癖もつけられないぐらいにルールを厳守することの間には大きな開きがある。
記事で言及されているレベッカ・バークはホストファミリーの家事を手伝っていた。いままで誰も問題にしていなかった旅行のある形態だけど、それが旅行者に許可されていない「労働」になるということで、拘束され、独房を経由して拘留施設に送られ、強制送還された。

アメリカ人CEOが日本に来てラーメンを食う写真を投稿した時、それが事業のプロモーションになるからといって(バークが「稼いだ宿泊費」の比ではないプロモーションになるのは確実だ)手錠をかけて独房に入れ、入管に拘留して臭い飯を食わせた末にエコノミーで叩き返したらどうなると思う?

newsweek.com/britain-issues-tr

0

関東大震災における朝鮮人等虐殺はおろか、七三一部隊が人体実験をおこなったことも、公文書が残っているにもかかわらず政府は歴史的事実と認めないというのか。恥を知れ。

戦後80年を問う 「731部隊」について 2025.3.21
youtu.be/Bz1lzkKtbHY

0

this FreeBSD rc.d issue with ntpd/svcj is actually pretty interesting and affects more than ntpd -- i think it's broken nfsd too, although i didn't test that. and it's not clear how to fix it.

the basic problem is that with svcj enabled, ${svc}_precmd is run in a different shell than ${svc}_start, which means environment changes from precmd aren't visible in the start function. since ntpd sets $command_args in precmd, this means it starts without any command-line args.

overriding ${svc}_start is not desirable because then you miss all the magic that rc.subr does in its default start method, including the svcj stuff.

running precmd in the svcj before start doesn't work either, because ntpd needs to load a kernel module in its precmd.

i think the only fix here is to split precmd into two things, perhaps one called 'setup’ that does what precmd does now, and ‘precmd' that runs before start in the svcj. then ntpd's 'setup' could load the kernel modules etc., and its 'precmd' could set the command line args.

unless anyone has a better idea?

0
0
0
0

유루메 Yurume replied to the below article:

Revisiting Java's Checked Exceptions: An Underappreciated Type Safety Feature

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub

Despite their bad reputation in the Java community, checked exceptions provide superior type safety comparable to Rust's Result<T, E> or Haskell's Either a b—we've been dismissing one of Java's best features all along.

Introduction

Few features in Java have been as consistently criticized as checked exceptions. Modern Java libraries and frameworks often go to great lengths to avoid them. Newer JVM languages like Kotlin have abandoned them entirely. Many experienced Java developers consider them a design mistake.

But what if this conventional wisdom is wrong? What if checked exceptions represent one of Java's most forward-thinking features?

In this post, I'll argue that Java's checked exceptions were ahead of their time, offering many of the same type safety benefits that are now celebrated in languages like Rust and Haskell. Rather than abandoning this feature, we should consider how to improve it to work better with modern Java's features.

Understanding Java's Exception Handling Model

To set the stage, let's review how Java's exception system works:

  • Unchecked exceptions (subclasses of RuntimeException or Error): These don't need to be declared or caught. They typically represent programming errors (NullPointerException, IndexOutOfBoundsException) or unrecoverable conditions (OutOfMemoryError).

  • Checked exceptions (subclasses of Exception but not RuntimeException): These must either be caught with try/catch blocks or declared in the method signature with throws. They represent recoverable conditions that are outside the normal flow of execution (IOException, SQLException).

Here's how this works in practice:

// Checked exception - compiler forces you to handle or declare it
public void readFile(String path) throws IOException {
    Files.readAllLines(Path.of(path));
}

// Unchecked exception - no compiler enforcement
public void processArray(int[] array) {
    int value = array[array.length + 1]; // May throw ArrayIndexOutOfBoundsException
}

The Type Safety Argument for Checked Exceptions

At their core, checked exceptions are a way of encoding potential failure modes into the type system via method signatures. This makes certain failure cases part of the API contract, forcing client code to explicitly handle these cases.

Consider this method signature:

public byte[] readFileContents(String filePath) throws IOException

The throws IOException clause tells us something critical: this method might fail in ways related to IO operations. The compiler ensures you can't simply ignore this fact. You must either:

  1. Handle the exception with a try-catch block
  2. Propagate it by declaring it in your own method signature

This type-level representation of potential failures aligns perfectly with principles of modern type-safe programming.

Automatic Propagation: A Hidden Advantage

One often overlooked advantage of Java's checked exceptions is their automatic propagation. Once you declare a method as throws IOException, any exception that occurs is automatically propagated to the caller without additional syntax.

Compare this with Rust, where you must use the ? operator every time you call a function that returns a Result:

// Rust requires explicit propagation with ? for each call
fn read_and_process(path: &str) -> Result<(), std::io::Error> {
    let content = std::fs::read_to_string(path)?;
    process_content(&content)?;
    Ok(())
}

// Java automatically propagates exceptions once declared
void readAndProcess(String path) throws IOException {
    String content = Files.readString(Path.of(path));
    processContent(content); // If this throws IOException, it's automatically propagated
}

In complex methods with many potential failure points, Java's approach leads to cleaner code by eliminating the need for repetitive error propagation markers.

Modern Parallels: Result Types in Rust and Haskell

The approach of encoding failure possibilities in the type system has been adopted by many modern languages, most notably Rust with its Result<T, E> type and Haskell with its Either a b type.

In Rust:

fn read_file_contents(file_path: &str) -> Result<Vec<u8>, std::io::Error> {
    std::fs::read(file_path)
}

When calling this function, you can't just ignore the potential for errors—you need to handle both the success case and the error case, often using the ? operator or pattern matching.

In Haskell:

readFileContents :: FilePath -> IO (Either IOException ByteString)
readFileContents path = try $ BS.readFile path

Again, the caller must explicitly deal with both possible outcomes.

This is fundamentally the same insight that motivated Java's checked exceptions: make failure handling explicit in the type system.

Valid Criticisms of Checked Exceptions

If checked exceptions are conceptually similar to these widely-praised error handling mechanisms, why have they fallen out of favor? There are several legitimate criticisms:

1. Excessive Boilerplate in the Call Chain

The most common complaint is the boilerplate required when propagating exceptions up the call stack:

void methodA() throws IOException {
    methodB();
}

void methodB() throws IOException {
    methodC();
}

void methodC() throws IOException {
    // Actual code that might throw IOException
}

Every method in the chain must declare the same exception, creating repetitive code. While automatic propagation works well within a method, the explicit declaration in method signatures creates overhead.

2. Poor Integration with Functional Programming

Java 8 introduced lambdas and streams, but checked exceptions don't play well with them:

// Won't compile because map doesn't expect functions that throw checked exceptions
List<String> fileContents = filePaths.stream()
    .map(path -> Files.readString(Path.of(path))) // Throws IOException
    .collect(Collectors.toList());

This forces developers to use awkward workarounds:

List<String> fileContents = filePaths.stream()
    .map(path -> {
        try {
            return Files.readString(Path.of(path));
        } catch (IOException e) {
            throw new UncheckedIOException(e); // Wrap in an unchecked exception
        }
    })
    .collect(Collectors.toList());

3. Interface Evolution Problems

Adding a checked exception to an existing method breaks all implementing classes and calling code. This makes evolving interfaces over time difficult, especially for widely-used libraries and frameworks.

4. Catch-and-Ignore Anti-Pattern

The strictness of checked exceptions can lead to the worst possible outcome—developers simply catching and ignoring exceptions to make the compiler happy:

try {
    // Code that might throw
} catch (Exception e) {
    // Do nothing or just log
}

This is worse than having no exception checking at all because it provides a false sense of security.

Improving Checked Exceptions Without Abandoning Them

Rather than abandoning checked exceptions entirely, Java could enhance the existing system to address these legitimate concerns. Here are some potential improvements that preserve the type safety benefits while addressing the practical problems:

1. Allow lambdas to declare checked exceptions

One of the biggest pain points with checked exceptions today is their incompatibility with functional interfaces. Consider how much cleaner this would be:

// Current approach - forced to handle or wrap exceptions inline
List<String> contents = filePaths.stream()
    .map(path -> {
        try {
            return Files.readString(Path.of(path));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    })
    .collect(Collectors.toList());

// Potential future approach - lambdas can declare exceptions
List<String> contents = filePaths.stream()
    .map((String path) throws IOException -> Files.readString(Path.of(path)))
    .collect(Collectors.toList());

This would require updating functional interfaces to support exception declarations:

@FunctionalInterface
public interface Function<T, R, E extends Exception> {
    R apply(T t) throws E;
}

2. Generic exception types in throws clauses

Another powerful enhancement would be allowing generic type parameters in throws clauses:

public <E extends Exception> void processWithException(Supplier<Void, E> supplier) throws E {
    supplier.get();
}

This would enable much more flexible composition of methods that work with different exception types, bringing some of the flexibility of Rust's Result<T, E> to Java's existing exception system.

3. Better support for exception handling in functional contexts

Unlike Rust which requires the ? operator for error propagation, Java already automatically propagates checked exceptions when declared in the method signature. What Java needs instead is better support for checked exceptions in functional contexts:

// Current approach for handling exceptions in streams
List<String> contents = filePaths.stream()
    .map(path -> {
        try {
            return Files.readString(Path.of(path));
        } catch (IOException e) {
            throw new RuntimeException(e); // Lose type information
        }
    })
    .collect(Collectors.toList());

// Hypothetical improved API
List<String> contents = filePaths.stream()
    .mapThrowing(path -> Files.readString(Path.of(path))) // Preserves checked exception
    .onException(IOException.class, e -> logError(e))
    .collect(Collectors.toList());

4. Integration with Optional<T> and Stream<T> APIs

The standard library could be enhanced to better support operations that might throw checked exceptions:

// Hypothetical API
Optional<String> content = Optional.ofThrowable(() -> Files.readString(Path.of("file.txt")));
content.ifPresentOrElse(
    this::processContent,
    exception -> log.error("Failed to read file", exception)
);

Comparison with Other Languages' Approaches

It's worth examining how other languages have addressed the error handling problem:

Rust's Result<T, E> and ? operator

Rust's approach using Result<T, E> and the ? operator shows how propagation can be made concise while keeping the type safety benefits. The ? operator automatically unwraps a successful result or returns the error to the caller, making propagation more elegant.

However, Rust's approach requires explicit propagation at each step, which can be more verbose than Java's automatic propagation in certain scenarios.

Kotlin's Approach

Kotlin made all exceptions unchecked but provides functional constructs like runCatching that bring back some type safety in a more modern way:

val result = runCatching {
    Files.readString(Path.of("file.txt"))
}

result.fold(
    onSuccess = { content -> processContent(content) },
    onFailure = { exception -> log.error("Failed to read file", exception) }
)

This approach works well with Kotlin's functional programming paradigm but lacks compile-time enforcement.

Scala's Try[T], Either[A, B], and Effect Systems

Scala offers Try[T], Either[A, B], and various effect systems that encode errors in the type system while integrating well with functional programming:

import scala.util.Try

val fileContent: Try[String] = Try {
  Source.fromFile("file.txt").mkString
}

fileContent match {
  case Success(content) => processContent(content)
  case Failure(exception) => log.error("Failed to read file", exception)
}

This approach preserves type safety while fitting well with Scala's functional paradigm.

Conclusion

Java's checked exceptions were a pioneering attempt to bring type safety to error handling. While the implementation has shortcomings, the core concept aligns with modern type-safe approaches to error handling in languages like Rust and Haskell.

Copying Rust's Result<T, E> might seem like the obvious solution, but it would represent a radical departure from Java's established paradigms. Instead, targeted enhancements to the existing checked exceptions system—like allowing lambdas to declare exceptions and supporting generic exception types—could preserve Java's unique approach while addressing its practical limitations.

The beauty of such improvements is that they'd maintain backward compatibility while making checked exceptions work seamlessly with modern Java features like lambdas and streams. They would acknowledge that the core concept of checked exceptions was sound—the problem was in the implementation details and their interaction with newer language features.

So rather than abandoning checked exceptions entirely, perhaps we should recognize them as a forward-thinking feature that was implemented before its time. As Java continues to evolve, we have an opportunity to refine this system rather than replace it.

In the meantime, next time you're tempted to disparage checked exceptions, remember: they're not just an annoying Java quirk—they're an early attempt at the same type safety paradigm that newer languages now implement with much celebration.

What do you think? Could these improvements make checked exceptions viable for modern Java development? Or is it too late to salvage this controversial feature? I'm interested in hearing your thoughts in the comments.

Read more →
0
0
3
0

Emelia 👸🏻 replied to the below article:

Revisiting Java's Checked Exceptions: An Underappreciated Type Safety Feature

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub

Despite their bad reputation in the Java community, checked exceptions provide superior type safety comparable to Rust's Result<T, E> or Haskell's Either a b—we've been dismissing one of Java's best features all along.

Introduction

Few features in Java have been as consistently criticized as checked exceptions. Modern Java libraries and frameworks often go to great lengths to avoid them. Newer JVM languages like Kotlin have abandoned them entirely. Many experienced Java developers consider them a design mistake.

But what if this conventional wisdom is wrong? What if checked exceptions represent one of Java's most forward-thinking features?

In this post, I'll argue that Java's checked exceptions were ahead of their time, offering many of the same type safety benefits that are now celebrated in languages like Rust and Haskell. Rather than abandoning this feature, we should consider how to improve it to work better with modern Java's features.

Understanding Java's Exception Handling Model

To set the stage, let's review how Java's exception system works:

  • Unchecked exceptions (subclasses of RuntimeException or Error): These don't need to be declared or caught. They typically represent programming errors (NullPointerException, IndexOutOfBoundsException) or unrecoverable conditions (OutOfMemoryError).

  • Checked exceptions (subclasses of Exception but not RuntimeException): These must either be caught with try/catch blocks or declared in the method signature with throws. They represent recoverable conditions that are outside the normal flow of execution (IOException, SQLException).

Here's how this works in practice:

// Checked exception - compiler forces you to handle or declare it
public void readFile(String path) throws IOException {
    Files.readAllLines(Path.of(path));
}

// Unchecked exception - no compiler enforcement
public void processArray(int[] array) {
    int value = array[array.length + 1]; // May throw ArrayIndexOutOfBoundsException
}

The Type Safety Argument for Checked Exceptions

At their core, checked exceptions are a way of encoding potential failure modes into the type system via method signatures. This makes certain failure cases part of the API contract, forcing client code to explicitly handle these cases.

Consider this method signature:

public byte[] readFileContents(String filePath) throws IOException

The throws IOException clause tells us something critical: this method might fail in ways related to IO operations. The compiler ensures you can't simply ignore this fact. You must either:

  1. Handle the exception with a try-catch block
  2. Propagate it by declaring it in your own method signature

This type-level representation of potential failures aligns perfectly with principles of modern type-safe programming.

Automatic Propagation: A Hidden Advantage

One often overlooked advantage of Java's checked exceptions is their automatic propagation. Once you declare a method as throws IOException, any exception that occurs is automatically propagated to the caller without additional syntax.

Compare this with Rust, where you must use the ? operator every time you call a function that returns a Result:

// Rust requires explicit propagation with ? for each call
fn read_and_process(path: &str) -> Result<(), std::io::Error> {
    let content = std::fs::read_to_string(path)?;
    process_content(&content)?;
    Ok(())
}

// Java automatically propagates exceptions once declared
void readAndProcess(String path) throws IOException {
    String content = Files.readString(Path.of(path));
    processContent(content); // If this throws IOException, it's automatically propagated
}

In complex methods with many potential failure points, Java's approach leads to cleaner code by eliminating the need for repetitive error propagation markers.

Modern Parallels: Result Types in Rust and Haskell

The approach of encoding failure possibilities in the type system has been adopted by many modern languages, most notably Rust with its Result<T, E> type and Haskell with its Either a b type.

In Rust:

fn read_file_contents(file_path: &str) -> Result<Vec<u8>, std::io::Error> {
    std::fs::read(file_path)
}

When calling this function, you can't just ignore the potential for errors—you need to handle both the success case and the error case, often using the ? operator or pattern matching.

In Haskell:

readFileContents :: FilePath -> IO (Either IOException ByteString)
readFileContents path = try $ BS.readFile path

Again, the caller must explicitly deal with both possible outcomes.

This is fundamentally the same insight that motivated Java's checked exceptions: make failure handling explicit in the type system.

Valid Criticisms of Checked Exceptions

If checked exceptions are conceptually similar to these widely-praised error handling mechanisms, why have they fallen out of favor? There are several legitimate criticisms:

1. Excessive Boilerplate in the Call Chain

The most common complaint is the boilerplate required when propagating exceptions up the call stack:

void methodA() throws IOException {
    methodB();
}

void methodB() throws IOException {
    methodC();
}

void methodC() throws IOException {
    // Actual code that might throw IOException
}

Every method in the chain must declare the same exception, creating repetitive code. While automatic propagation works well within a method, the explicit declaration in method signatures creates overhead.

2. Poor Integration with Functional Programming

Java 8 introduced lambdas and streams, but checked exceptions don't play well with them:

// Won't compile because map doesn't expect functions that throw checked exceptions
List<String> fileContents = filePaths.stream()
    .map(path -> Files.readString(Path.of(path))) // Throws IOException
    .collect(Collectors.toList());

This forces developers to use awkward workarounds:

List<String> fileContents = filePaths.stream()
    .map(path -> {
        try {
            return Files.readString(Path.of(path));
        } catch (IOException e) {
            throw new UncheckedIOException(e); // Wrap in an unchecked exception
        }
    })
    .collect(Collectors.toList());

3. Interface Evolution Problems

Adding a checked exception to an existing method breaks all implementing classes and calling code. This makes evolving interfaces over time difficult, especially for widely-used libraries and frameworks.

4. Catch-and-Ignore Anti-Pattern

The strictness of checked exceptions can lead to the worst possible outcome—developers simply catching and ignoring exceptions to make the compiler happy:

try {
    // Code that might throw
} catch (Exception e) {
    // Do nothing or just log
}

This is worse than having no exception checking at all because it provides a false sense of security.

Improving Checked Exceptions Without Abandoning Them

Rather than abandoning checked exceptions entirely, Java could enhance the existing system to address these legitimate concerns. Here are some potential improvements that preserve the type safety benefits while addressing the practical problems:

1. Allow lambdas to declare checked exceptions

One of the biggest pain points with checked exceptions today is their incompatibility with functional interfaces. Consider how much cleaner this would be:

// Current approach - forced to handle or wrap exceptions inline
List<String> contents = filePaths.stream()
    .map(path -> {
        try {
            return Files.readString(Path.of(path));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    })
    .collect(Collectors.toList());

// Potential future approach - lambdas can declare exceptions
List<String> contents = filePaths.stream()
    .map((String path) throws IOException -> Files.readString(Path.of(path)))
    .collect(Collectors.toList());

This would require updating functional interfaces to support exception declarations:

@FunctionalInterface
public interface Function<T, R, E extends Exception> {
    R apply(T t) throws E;
}

2. Generic exception types in throws clauses

Another powerful enhancement would be allowing generic type parameters in throws clauses:

public <E extends Exception> void processWithException(Supplier<Void, E> supplier) throws E {
    supplier.get();
}

This would enable much more flexible composition of methods that work with different exception types, bringing some of the flexibility of Rust's Result<T, E> to Java's existing exception system.

3. Better support for exception handling in functional contexts

Unlike Rust which requires the ? operator for error propagation, Java already automatically propagates checked exceptions when declared in the method signature. What Java needs instead is better support for checked exceptions in functional contexts:

// Current approach for handling exceptions in streams
List<String> contents = filePaths.stream()
    .map(path -> {
        try {
            return Files.readString(Path.of(path));
        } catch (IOException e) {
            throw new RuntimeException(e); // Lose type information
        }
    })
    .collect(Collectors.toList());

// Hypothetical improved API
List<String> contents = filePaths.stream()
    .mapThrowing(path -> Files.readString(Path.of(path))) // Preserves checked exception
    .onException(IOException.class, e -> logError(e))
    .collect(Collectors.toList());

4. Integration with Optional<T> and Stream<T> APIs

The standard library could be enhanced to better support operations that might throw checked exceptions:

// Hypothetical API
Optional<String> content = Optional.ofThrowable(() -> Files.readString(Path.of("file.txt")));
content.ifPresentOrElse(
    this::processContent,
    exception -> log.error("Failed to read file", exception)
);

Comparison with Other Languages' Approaches

It's worth examining how other languages have addressed the error handling problem:

Rust's Result<T, E> and ? operator

Rust's approach using Result<T, E> and the ? operator shows how propagation can be made concise while keeping the type safety benefits. The ? operator automatically unwraps a successful result or returns the error to the caller, making propagation more elegant.

However, Rust's approach requires explicit propagation at each step, which can be more verbose than Java's automatic propagation in certain scenarios.

Kotlin's Approach

Kotlin made all exceptions unchecked but provides functional constructs like runCatching that bring back some type safety in a more modern way:

val result = runCatching {
    Files.readString(Path.of("file.txt"))
}

result.fold(
    onSuccess = { content -> processContent(content) },
    onFailure = { exception -> log.error("Failed to read file", exception) }
)

This approach works well with Kotlin's functional programming paradigm but lacks compile-time enforcement.

Scala's Try[T], Either[A, B], and Effect Systems

Scala offers Try[T], Either[A, B], and various effect systems that encode errors in the type system while integrating well with functional programming:

import scala.util.Try

val fileContent: Try[String] = Try {
  Source.fromFile("file.txt").mkString
}

fileContent match {
  case Success(content) => processContent(content)
  case Failure(exception) => log.error("Failed to read file", exception)
}

This approach preserves type safety while fitting well with Scala's functional paradigm.

Conclusion

Java's checked exceptions were a pioneering attempt to bring type safety to error handling. While the implementation has shortcomings, the core concept aligns with modern type-safe approaches to error handling in languages like Rust and Haskell.

Copying Rust's Result<T, E> might seem like the obvious solution, but it would represent a radical departure from Java's established paradigms. Instead, targeted enhancements to the existing checked exceptions system—like allowing lambdas to declare exceptions and supporting generic exception types—could preserve Java's unique approach while addressing its practical limitations.

The beauty of such improvements is that they'd maintain backward compatibility while making checked exceptions work seamlessly with modern Java features like lambdas and streams. They would acknowledge that the core concept of checked exceptions was sound—the problem was in the implementation details and their interaction with newer language features.

So rather than abandoning checked exceptions entirely, perhaps we should recognize them as a forward-thinking feature that was implemented before its time. As Java continues to evolve, we have an opportunity to refine this system rather than replace it.

In the meantime, next time you're tempted to disparage checked exceptions, remember: they're not just an annoying Java quirk—they're an early attempt at the same type safety paradigm that newer languages now implement with much celebration.

What do you think? Could these improvements make checked exceptions viable for modern Java development? Or is it too late to salvage this controversial feature? I'm interested in hearing your thoughts in the comments.

Read more →
0
0
3
0
0
0
0
0
0
0