Hacker News new | comments | show | ask | jobs | submit login
A Sinatra-inspired micro web framework for Java (sparkjava.com)
121 points by codenut on Sept 12, 2013 | hide | past | web | favorite | 93 comments

This project is nice but it's important to mention that there is actually a "Java micro web framework" standard called JAX-RS that is pretty widely adopted (with solid open-source implementations by JBoss, Apache, Oracle and others). Here's a rather old comparison of JAX-RS implementations: http://www.infoq.com/news/2008/10/jaxrs-comparison

Its most notable implementation is perhaps Jersey (https://jersey.java.net/documentation/latest/getting-started...), which is used, for example, by Dropwizard (mentioned in another comment).

Some of these implementations include important features like health and performance monitoring.

JAX-RS still has the same problem with nesting and redundancy, doesn't fit the word "micro" or draw parallels with Sinatra.

To quote another commenter, here's JAX-RS code:

  public class HelloWorld {
    public String get() {
      return "Hello World";
Seems very Sinatra-like to me.

You don't see all the redundancies in that code?

I see the term "get" twice. The class name is redundant with the resource path. I suppose not a huge problem because many MVC frameworks require classes, but not even close to Sinatra.


  get '/hi' do
    "Hello World!"

You're counting required language keywords towards what makes a micro-framework? That's bullshit. You're never going to get away from some of the keywords in Java, nor does that make JAX-RS less 'micro'

Here is an example in Groovy using JAX-RS:

  class HelloWorld {

    def sayHello() {
      "Hello, world!"  
Almost as clean. The braces and parens make it a bit uglier; but those are just part of the language.

The point is that you shouldn't have to configure things like @Path because they can be inferred from the class name. Why not:

  class Hello {
    def get() {
      "Hello, world!"
I would prefer to see this and then let someone have a routing config somewhere else if they want to move the /hello endpoint.

Sounds like a preferential thing then; I prefer to keep everything together.

I've never liked the separate routing config approach. Play Framework uses(d?) it in the 1.x branch and I always found it annoying.

Similarly old-world Servlets were done that way too. You'd put the routing information in an XML file and the container would read it at start up.

It doesn't work how you want it, fine, don't use it. It's super brief for the rest of us.

Not only uglier, but a lot sloooooooower.

You're missing out on all the power that jax-rs gives you though with how it binds to things like jackson.


  public class CompanyResource {
    public Company getFromDB(@PathParam("id") String companyId) {
      return SomeDatabase.get(companyId);

  public class Company {
    List<Person> employees;

  public class Employee {
    String name;
    int age;
    Employee manager;
You start to understand how good jax-rs and modern Java can be when you actually start using it for something useful.

Using SparkJava this is what it looks like:

  get(new JsonTransformer("/companies/:id") {
    public Company handle(Request request, Response response) {
      return SomeDatabase.get(request.params(":id"));


  public class Company {
    List<Person> employees;

  public class Employee {
    String name;
    int age;
    Employee manager;

So it's got the same functionality as the industry accepted standard, same concision as the industry accepted standard, only the syntax is different. Why not just use the industry accepted standard syntax, then? Why isn't Spark a JAX-RS implementation?

I haven't written Java in a while and I've never seen any other examples of JAX-RS, so feel free to take this with a grain of salt. That said, in my opinion JAX-RS looks uglier, less clear, and more verbose than Spark, and I'd want an alternative to JAX-RS were I ever to write Java web code.

Agreed, Per Wendel the creator of Spark explains here too:


I have done JAX-RS, for small web apps where you aren't trying to build in the kitchen sink, Spark is looking nice. Spark doesn't seem to be trying to replace JAX-RS, it seems to just be making the syntax a bit easier and doing lightweight REST in a way I find unique and really applicable to small web apps. He uses plain Java main.

You can use a plain Java main with JAX-RS, too (when using an embedded server like, say, Jetty – just like Spark). Dropwizard does exactly that.

Well, I don't know... I'm sure Spark is great, but dropping a widely accepted, well established and quite liked standard just for an arguably "bit easier" syntax (especially when there haven't been any major complaints about the standard syntax) seems like the wrong tradeoff. I would have loved a JAX-RS implementation with other compelling features like performance, monitoring etc. Another good JAX-RS implementation is always welcome.

Concision is a virtue.

Further, not a big fan of annotations. Which is declarative programming for Java. In which case, just use a dynamic programming language.

I am a big fan of stepping thru code with a debugger. Which rules out programming via XML (eg Spring) and annotations.

You can step through all of Spring in your debugger with no problems, whether you configure it in xml or via annotations. Spring is not "programming in xml".

Also, Python has decorators and Python 3 will soon have function annotations.

step through all of Spring in your debugger with no problems

You misunderstand. Java breakpoints, yes. Spring breakpoints, no.

To troubleshoot Spring, the best you can do is trace (log) and inspect untyped nested hashmaps.

No thank you.

The IDE can sometimes pre-detect trivial errors. Whoopie.

Python has decorators and Python 3 will soon have function annotations.

My condolences.

Somehow having that much nesting and redundancy doesn't fit the word "micro" or draw parallels with Sinatra.


  get '/hi' do
    "Hello World!"

  public class HelloWorld {
     public static void main(String[] args) {      
        get(new Route("/hello") {
           public Object handle(Request request, Response response) {
              return "Hello World!";

All the nesting and redundancy come from Java itself. Verbose as it may be, after you use any language for a while you will see right through the syntax and it will not add (much) cognitive load to your work.

I think the value here is in having a micro framework that allows you to put together a web app without having to juggle a horribly complex abstraction hierarchy for even the simplest of tasks.

Syntax is what makes the framework micro.

Following your point about the cruft being from the language, Spring MVC is as "micro" as Spark. In fact, it is shorter.

Spring MVC:

  public class HelloController {
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public @ResponseBody String hello() {
      return "Hello World!";

  public class HelloWorld {
     public static void main(String[] args) {      
        get(new Route("/hello") {
           public Object handle(Request request, Response response) {
              return "Hello World!";

You don't call a framework "micro" based on the amount of code it takes to achieve certain tasks. You call it "micro" because it's small enough that you can learn and truly master in a short time (and you get that by cutting out as many non-essential features as possible).


Micro: http://www.sparkjava.com/readme.html

Not micro: http://docs.spring.io/spring/docs/3.2.x/spring-framework-ref...

The downside when using a micro framework is that you always walk a fine line between simplicity and code reuse. Because of this, as the application grows you do end up writing more code than with a complex framework because you slowly start reinventing the wheel -- this is the old argument you hear over and over (Flask vs Django, Sinatra vs Rails, BackboneJS vs AngularJS, etc.)

> All the nesting and redundancy come from Java itself

Hmm. How about:

    public String GET_hello(Request req) {
      return "Hello World";
The framework would use reflection to build the routing map.

Speaking of Sinatra-inspired web frameworks on the JVM have a look at Scalatra:



I liked most of it. But when it came to this atmosphere stuff, it was vietnam all over again!

What's wrong with those Scala developers?

They could make beautiful frameworks and APIs, always start to create crazy method names.

> "org.scalatra" %% "scalatra-atmosphere" % "2.2.1",

> send(("author" -> "system") ~ ("message" -> "Only json is allowed") ~ ("time" -> (new Date().getTime.toString )))

What's wrong with them?!

Same symptom is shown in Haskell circles. I suspect it might be due to the enthusiasts of these languages often being mathematicians. And mathematicians love crazy sigils, as we know.

probably :\

And this is rather sad.

You got a language which allows you to write nearly litteral code and still build stuff only mathematicans can understand.

yah they should've done something like:

    send(JSONSerialization.with(StringSerializer.class, DateTimeSerializer.class).serialize(JSONObjectFactory.put(PairFactory.of(String.class, String.class).getInstance("author", "system"), PairFactory.of(String.class, String.class).getInstance("message", "Only json is allowed"), PairFactory.of(String.class, DateTime.class).getInstance("time", new Date())).getInstance()))
no, it's just json4s dsl for constructing json. If you wanted, you could've used constructors yourself:

    send(pretty(render(JObject(List(JField("author", JString("system")), JField("message", JString("Only json allowed")), JField("time", JString(new DateTime().getTime().toString()))))
usually scala libraries come with an easy to read/write DSL version of the API along with a more verbose java-style API shown above. Many libraries don't document with a plenty of examples. But, it really takes 10 minutes to learn DSL version in many cases. And, it gives you wings. There are plenty of benefits of DSL. You can research online about them.

I hate the Java style as much as this nonsense-operator-like-function-name style of Scala.

Why do they have to name them ~ % %%, if they can name them ANYTHING. Just to make the writing shorter?

It's like using variable names like a, b, c, data, value etc.

I want readable code, not short code by hook or by crook...

operators like +, -, /, %, ^, ... read much better once you learn their meanings and how to use them.

it's okay to learn things.

Scalatra had some good features, but the installation instructions required an entirely new build or package system, giter8, which in turn requires another package, conscript. Two packages to install another package? This is just an unnecessary barrier to entry.

The problem I have with Scalatra is that they seemed to be more focused on their Manning book than getting the core docs up to a good standard.

Open source software developers also need to pay bills.

Free documentation does not pay bills, books do. Assuming people don't pirate them.

yah, it required me to install a JVM. And JVM required me to purchase a personal computer that can host the JVM. And electricity and stuff. Such an unnecessary barrier to entry.

I think it's simpler to tribute Sinatra in Java by annotating methods:

  String myMethod() {
    return "Hello World!";
I made a prototype of this (including extracting arguments Sinatra-style, which I think is its coolest bit), and didn't encounter any serious problems with this approach.

I like reducing the number of annotations which is one reason I like the SparkJava approach.

Java 8 lambdas might shorten their approach too.

I've reviewed Lambdas and still don't think the syntax is clearer to me than just using an Anonymous Class. Either way, less configuration and less annotations, whatever lets me do that I could buy into as long as the debugging isn't a mess.

Definitely something that was needed, microframeworks are great. Restlet I used long ago but still very Java enterprisey. Play! is like Java's Django/Rails and now Spark is the Sinatra, simple and minimal. Scalatra mentioned here looks great as well, all these wonderful minimal Java toys.

This is fantastic. I know a lot of programming languages and I love them all for different reasons, but I think I am most productive and most likely to produce error-free programs in Java. All the verbosity that people hate is a feature to me, I know exactly what my program is doing.

However, I have done 95% of my web development in Ruby due to the Sinatra and Rails frameworks. The developer experience is too good to ignore. I'm hoping this can replace my Sinatra use case ... I don't think the magic of Rails could ever be replicated in Java.

Where does this stand against dropwizard?

Personally, I would use dropwizard. Spark's key point seems to be avoiding annotation based constructs.

Compare (Spark):

  public class HelloWorld {
     public static void main(String[] args) {      
        get(new Route("/hello") {
           public Object handle(Request request, Response response) {
              return "Hello World!";
to (JAX-RS, which dropwizard uses)

  public class HelloWorld {
    public String get() {
      return "Hello World";

Annotations are great until you want to do ANYTHING dynamic. "It's a trap!" Everytime without fail I want to do this kind of thing I end up ripping it all out later when I need more flexibility.

If Dropwizard lets me fall back to non-annotations, then awesome.

Really, five minutes ago I didn't know either of these projects existed, so right now I'm feeling a little giddy either way you slice it!

I don't know what you mean by dynamic but dropwizard is just a selection of other libraries wired together into a framework with supporting code (config, etc) which is built for high performance HTTP services serving JSON, but it can do HTML rendering. The above code is compliant with any JAX-RS implementation, JAX-RS is probably the most well done Java spec I've ever worked with which isn't saying much, but it doesn't suck.

I think by dynamic, they mean that you can't really edit the annotation at runtime. In this case, it would be that you can't choose the HTTP verb using an if statement, for example. Other annotations have parameters, which you might also want to tweak.

Annotations trade flexibility for succinctness and readability. That's often a great reasonable tradeoff and a good place to start, but you might find yourself ripping them out later.

One example would be that with Spark you could procedurally create and register multiple Route instances. That is not an option with an annotation-based API that requires you to have a distinct annotated class per route.

More abstractly, one of the characteristics of object-oriented design is the use of polymorphic objects. APIs which require you to define monomorphic annotated classes violate this and lose generality as a result. They are effectively class-oriented or method-oriented rather than true OO.

Why would you want that feature? Also I'm pretty sure nothing about jaxrs annotations prevents such a thing from being possible but the servlet container may get in your way of doing it easily.

Why would you want to procedurally generate routes or verbs? I dunno, perhaps there might be a use case where one would want to define them in configuration or a database. But being able to do something like this is not a "feature," it's just the inherent flexibility of expressing semantics in code.

Why would you want an API that breaks the flexibility of code by forcing it to be glorified XML?

By dynamic I think he just means that annotations are a really hacky way to do things. You use annotations in place of java code, and while you can tweak java code you can't tweak what an annotation does or how it does it.

> By dynamic I think he just means that annotations are a really hacky way to do things.


> You use annotations in place of java code, and while you can tweak java code you can't tweak what an annotation does or how it does it.

Why can't you tweak an annotation? Annotations are built into the language, the entire point of them is to allow code to detect their presence in the bytecode and thus react at runtime. e.g. wrap a method marked as @Transactional in a database transaction.

You're right--instead of "can't" I should have said that a lot fewer Java developers are willing to mess with them, vs. changing code.

Edit: and they're hacky because they're magical--it's entirely not obvious what they're doing.

This is sort of getting off topic, but... I agree that annotations are magical. The good news is that they announce magic is happening. When I see unfamiliar annotations in Java code I know to look at their javadocs. I like that kind of magic much more than magic by convention (e.g. the location of the given file on disk).

Dropwizard is very well implemented and uses annotations for achieving a very concise syntax as far as Java goes. No XML too, which is a big plus for me.

But when I though how many lines some webapp I was writing would take in Clojure, I went to the corner of the room and cried.

My conclusion for now is that "first versions" should be written in dynamic languages.

Right. This project should be compared to Jersey (the JAX-RS implementation Dropwizard uses). Dropwizard has other components as well (for monitoring, DB etc.).

This looks really cool, I can't wait to hack around with this!

Question for the community: I don't have enough experience to know how this stacks up against other servers out there. Also, how do the people HN enjoy using java as a webserver (lang-wise, scaling-wise, etc)?

Any feedback would be much appreciated!!

I enjoy using it, except when we are forced to target Websphere.

JBoss, Jetty and Tomcat are pretty good in terms of taking care of load in heavy sites.

Most JVMs (Oracle, IBM, Azul, Aonix, ...) are able to achieve very good performance levels.

There is the tooling one gets, not only in terms of IDE support, but also for monitoring the state of the server processes and how load is being affected.

Finally if Java the language, doesn't appeal you, there are plenty of languages that target the JVM as well.

I would use Java for GWT, if nothing else. Being able to write a huge and complex application for the web, using Java both for the client and server code is something I would not want to live without (links in my profile for examples).

But besides that, Java is extremely mature, has great tooling, huge community and of course an enormous ecosystem with all the third party tools and libraries that come with it. The language is a bit primitive (but improving with each release), and the 'enterprise consultant' mindset of complexity for its own sake can be a turn-off, but overall, yeah, I like using Java.

Also, how do the people HN enjoy using java as a webserver (lang-wise, scaling-wise, etc)?

I mostly like Java, and find a lot of the criticisms to be somewhat misguided, or reducing to quibbling over minutiae. But the Java ecosystem has a lot going for it, and despite how some people think "enterprise-y" is a pejorative, a lot of those "enterprise" features are actually very handy when you're, well, building a serious enterprise solution. Sure, if you're building the n thousandth MVP of a new cat-picture-sharing site, you don't need Java. But if you're building Amazon.com (the ecommerce side, not AWS) you might just find that JTA, JMS, JMX, JAX-RS, and some of the other "enterprise-y" stuff in Java is pretty fucking useful.

From a scalability standpoint, using modern appservers, Java scales horizontally just fine. And since Java runs on just about everything from watches to IBM zSeries mainframes, it also scales vertically pretty well.

All of that said, I prefer to do my actual coding in Groovy, as opposed to "pure" Java, but Groovy is almost a strict superset of Java, just with nicer syntax and some new features. But whether you're using Groovy or Java, everything I said above still applies, since you're still part of that overall JVM ecosystem.

Groovy's much slower than Java. The statically typed mode shipped in Groovy 2 a year ago was written by one lone programmer they brougt in, is still buggy, and isn't even used in the Grails codebase afaik.

You're right, it is slower. But for my purposes, it's fast enough. YMMV, of course.

I've never enjoyed using Java.

If you have time look at Scala (Scalatra/Play Framework) or Clojure (Ring). You can reduce the size of your codebase dramatically.

Ive enjoyed writing code in scala, not until I compile it. It takes a long to compile. I agree with you, java is not enjoyable but we cannot deny the fact it still gets the job done.

That used to be true earlier, but the compile times in Scala are pretty good these days. Also I hear that a faster compiler is expected with the 2.11 release. Incremental compilation in Eclipse is already real-time.

But in general, IMO, compile times shouldn't be a major factor in choosing a language; expressiveness and run-times are more important.

That's only partially true. Compile times are absolutely a factor in development time for the same reason that how long your test suite takes to run is a factor. A faster feedback loop can shorten dev cycles. If nothing else, it's more fun to have faster compiles.

This is one reason Go is so appealing to many people. The compile times make it "feel" like ruby or python or php in terms of "hit refresh and the change is there" style of development. That is a huge difference from, hit save and wait a minute for my java project to compile, and push out to tomcat a minute later. Even with JRebel the best I've seen is a 2-5 second page change refresh cycle in Scala and a minimum 5 second test suite reload time using SBT.

Scala is a beautiful language, but compared to Go, it's a very slow dev cycle.

> compile times shouldn't be a major factor in choosing a language

Not sure about that, I'm using C# to write office plugins and it takes forever to start up each time. Often I forget what I'm supposed to be looking at by the time it's started.

Java scales well as long as memory is not the issue. Java's problem is memory usage and above all a deeply ingrained culture of complexity.

On modern server hardware memory is seldom an issue, but I would agree about the culture of complexity BeanSingletonProxyAbstractWorkerListener

Memory is the dominant cost factor when you rent a server, but my main issue with Java's memory usage is that a single (Oracle) VM instance becomes incresingly unusable above 4GB of RAM due to garbage collector pauses. If you can distribute your work over many VMs that's fine, but if the application needs a lot of data in memory then Java just isn't a viable solution. At least not the Oracle VM and Azul is too expensive.

I had looked at Spark a couple of weeks ago. I'm looking for a lightweight service-y sort of framework which just happens to have a good HTTP service available out-of-the-box. My application does back-end processing and coordinates a bunch of data feeds, thick-client HTTP requests, background processing, etc. all together. Spark was too web-centric (not a bad thing, just not what I need). OSGi was an attempt to be what I want, but its requirement of classpath isolation (a good idea conceptually) is onerous in practice. Right now, I'm using a homespun amalgam of Grizzly, Guice, and java.util.concurrent (for thread pooling), and other stuff. I really didn't want to build my own container. I'm intrigued by Apache Karaf, but OSGi is a huge pill to swallow (If you haven't debugged OSGi classloading issues, you'd be in for a treat).

In a nutshell, I want a service lifecycle container which allows me to write small, lightweight, modular services which depend on each other. The container should provide for cross-cutting concerns like monitoring, management, configuration, logging, auditability (I have concrete definitions for these things -- they're not just abstract biz-speak to me). HTTP should be an out-of-the-box, optional module. A service which exposes another service via a RESTful interface and depends upon the HTTP service should be another. For my application, services which listen to multicast data streams are just as important interfaces to the world as JSON-over-HTTP-via-REST. I want to write the HelloWorld method body and be able to do stuff like: expose it via a RESTful interface, invoke it every N seconds, inject an interface exposing the HelloWorld contract into other services, etc. When I want to know how my HelloWorld service is performing, there's a pre-built web interface which provides New Relic-esque views. I'd like to capture audit trails of the transactional flows through my services from an origination point (HTTP call, scheduled job, etc.) so I can translate failures, poor performance, usage rates, etc. into meaningful information (I wrote a poor man's version of this myself, and it's been quite useful).

Does anything like this exist? I sure can't find it. Modern JBoss (now Wildfly) might actually be closer to my requirements than I think. Perhaps I should look at the work they're doing on Version 8.

Using Grizzly/Netty as the core comm component seems like a good idea. Jersey integrates will with Grizzly, I think.

Dropwizard packages together Jersey on top of Jetty with good monitoring, but doesn't take care of other stuff like multicast. Nothing prevents you from adding Netty/Grizzly into the mix, though.

Actually, the modularity, composability and cross-cutting concerns you mention suggest Spring – not lightweight by any means, but neither are your requirements :)

I think you can even use Jersey with Spring: http://www.infoq.com/articles/springmvc_jsx-rs

Hate to say it but Karaf does fit the bill. Use the right mvn plugins and it's mostly just boilerplate. I work with it every day and it's generally not the painful experience you think it is.

I've been using Dropwizard. It provides a nice way to lifecycle manage other objects, so I've used it as a platform for Thrift services with Jersey resources serving debugging information.

It's kind of a bastardization, but we have a pretty good mix of HTTP and not, so it's nice to standardize.

Ratpack Framework is a Sinatra-inspired framework for the JVM worth noting: http://www.ratpack-framework.org


  ratpack {
    handlers {
      get('hello') {
        response.send "Hello world!"
It can also be used (with somewhat less brevity) in good, old-fashioned Java.

An 0.9.0 release is expected in a few weeks and the project is undergoing rapid (and sometimes API-changing) development, but it looks very promising.

Have people dropped the 'com' from java projects now? Not arguing in favor, the whole thing is ridiculous to me (src/main/com/actual_files). Just curious.

It would actually be src/main/com/organization/project/actual_files

And there are good reasons for that - naming packages after internet domains prevents namespace clashes and is consistently used by most Java projects.

It's actually projects that diverge from this convention that stand out like a sore thumb, and many that did have adopted it eventually, like JUnit.

Depends on the dependency management they want to support. If you want to get your package into maven central making it easy to use, you seem to still have to own the domain in reverse order. aka if you want to publish a package with a com.foo.* you need to actually own foo.com.

There's also webbit-rest: https://github.com/webbit/webbit-rest

I wrote a semi-realtime implementation of the planning poker estimation technique at [1] using Spark and some template engine I found.

This was for a course in software project management with some time restraints, so only focused on getting it done.

[1] https://github.com/Zolomon/PlanningPoker

For what it's worth, Spark performs quite well in terms of request routing when compared to other JVM frameworks of various sizes [1].

[1] http://www.techempower.com/benchmarks/#section=data-r6&hw=ec...

Very nice! Had a look at this a year or so ago and it would be a great base for mocking up backends IMHO.

In a similar vein is Google SiteBricks: http://sitebricks.org/#home

Slightly heavier weight but with more features (templating, annotations, etc). Depends on Guice.

It is a nice one. I use Jelastic(www.jelastic.com) for my startup. What if we can have the same functionality there integrated. But this is needed at least the microframeworks are nice. Thinking that the same kind of thing can be also achieved with he framework integration in Jelastic.

Finally a web framework I can use. All the Java web frameworks I tried to use are way too enterprise-y.

You should expand your horizon once in a while. JAX-RS has ben her for a while. Ditto with Spring MVC.

It really bugs me that they decided to clone the Sinatra README page, keeping most of the docs on one giant list instead of a more traditional multi-page doc approach. See here: http://www.sparkjava.com/readme.html

In addition, some of their docs link to a Google Code page, which keeps some examples on its index too: https://code.google.com/p/spark-java/

Lastly, it appears they're using GitHub for actual code, and, while they have a Github wiki created, it has no information.

I wish I could stress just how important it is to have all of the docs in one centralized location. Somewhere along the line, one of the five places they have examples will get out of date, and it'll be difficult to play spot the differences when it's replicated everywhere.

I love it! Just whipped up a couple of toy web services in 15 mins using Spark. They will be part of a technical interview coding task.

True, Spark may not be better/more concise than JAX-RS, but fun to try anyway.

Okay. I am holding the idiot ball here: How can we deploy an app developed with spark? What are our options? Heroku? AWS? I haven't seen anything regarding to deployment in the documentation.

Well, there's a howto on running on tomcat:


As for Heroku, I found this:


Generally, for a lot of apps the embedded Jetty webserver might be sufficient.

At the bottom of http://www.sparkjava.com/readme.html there are 2 options, an embedded web server (Jetty) and a configuration snippet for a standard web.xml file.

Check Running Spark on a Web Server, e.g. Tomcat in Readme section.

I feel like Keurig would be a no brainer as a name for a Java micro Web framework.

would love to see such a mf for c/c++...

not exactly a microframework, but an interesting approach to build a basis for it, like rack for sinatra.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact