{"id":19614695,"url":"https://github.com/activestate/java-demo","last_synced_at":"2025-09-05T03:35:51.857Z","repository":{"id":234358209,"uuid":"788734891","full_name":"ActiveState/java-demo","owner":"ActiveState","description":"Simple Maven Project for Java Demos","archived":false,"fork":false,"pushed_at":"2024-04-26T22:47:18.000Z","size":19,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":14,"default_branch":"main","last_synced_at":"2025-02-26T17:48:19.629Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ActiveState.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-19T01:26:46.000Z","updated_at":"2024-04-26T22:47:21.000Z","dependencies_parsed_at":"2024-04-26T00:07:46.441Z","dependency_job_id":"97fdb928-cf47-47af-963a-d6dd2b993d4c","html_url":"https://github.com/ActiveState/java-demo","commit_stats":null,"previous_names":["activestate/java-demo"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ActiveState/java-demo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ActiveState%2Fjava-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ActiveState%2Fjava-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ActiveState%2Fjava-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ActiveState%2Fjava-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ActiveState","download_url":"https://codeload.github.com/ActiveState/java-demo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ActiveState%2Fjava-demo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273707663,"owners_count":25153726,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-05T02:00:09.113Z","response_time":402,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-11T10:53:22.551Z","updated_at":"2025-09-05T03:35:46.840Z","avatar_url":"https://github.com/ActiveState.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"## REST API in pure Java without any frameworks\n\nThis is a demo application developed in Java 11 using \n[`jdk.httpserver`](https://docs.oracle.com/javase/10/docs/api/com/sun/net/httpserver/package-summary.html) module \nand a few additional Java libraries (like [vavr](http://www.vavr.io/), [lombok](https://projectlombok.org/)).\n\n## Genesis of this project\nI am a day-to-day Spring developer and I got used to this framework so much that I imagined how it would be to forget about it for a while\nand try to build completely pure Java application from scratch. \n\nI thought it could be interesting from learning perspective and a bit refreshing.\n\nWhen I started building this I often came across situations when I missed some features which Spring provides out of the box.\n\nAt that times, instead of switching on another Spring capability, I had to rethink it and develop it myself.\n\nIt occurred that for real business case I would probably still prefer to use Spring instead of reinventing a wheel.\n\nStill, I believe the exercise was pretty interesting experience.\n\n## Beginning.\nI will go through this exercise step by step but not always pasting a complete code in text\nbut you can always checkout each step from a separate branch.\nI started from empty `Application` main class. You can get an initial branch like that: \n\n```\ngit checkout step-1\n```\n\n## First endpoint\n\nThe starting point of the web application is `com.sun.net.httpserver.HttpServer` class. \nThe most simple `/api/hello` endpoint could look as below: \n\n```java\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.InetSocketAddress;\n\nimport com.sun.net.httpserver.HttpServer;\n\nclass Application {\n\n    public static void main(String[] args) throws IOException {\n        int serverPort = 8000;\n        HttpServer server = HttpServer.create(new InetSocketAddress(serverPort), 0);\n        server.createContext(\"/api/hello\", (exchange -\u003e {\n            String respText = \"Hello!\";\n            exchange.sendResponseHeaders(200, respText.getBytes().length);\n            OutputStream output = exchange.getResponseBody();\n            output.write(respText.getBytes());\n            output.flush();\n            exchange.close();\n        }));\n        server.setExecutor(null); // creates a default executor\n        server.start();\n    }\n}\n```\nWhen you run main program it will start web server at port `8000` and expose out first endpoint which is just printing `Hello!`, e.g. using curl:\n\n```bash\ncurl localhost:8000/api/hello\n```\n\nTry it out yourself from branch:\n\n```bash\ngit checkout step-2\n```\n\n## Support different HTTP methods\nOur first endpoint works like a charm but you will notice that no matter which HTTP method you'll use it will respond the same.\nE.g.: \n\n```bash\ncurl -X POST localhost:8000/api/hello\ncurl -X PUT localhost:8000/api/hello\n```\n\nThe first gotcha when building the API ourselves without a framework is that we need to add our own code to distinguish the methods, e.g.:\n\n```java\n        server.createContext(\"/api/hello\", (exchange -\u003e {\n\n            if (\"GET\".equals(exchange.getRequestMethod())) {\n                String respText = \"Hello!\";\n                exchange.sendResponseHeaders(200, respText.getBytes().length);\n                OutputStream output = exchange.getResponseBody();\n                output.write(respText.getBytes());\n                output.flush();\n            } else {\n                exchange.sendResponseHeaders(405, -1);// 405 Method Not Allowed\n            }\n            exchange.close();\n        }));\n```\n\nNow try again request: \n```bash\ncurl -v -X POST localhost:8000/api/hello\n```\nand the response would be like: \n\n```bash\n\u003e POST /api/hello HTTP/1.1\n\u003e Host: localhost:8000\n\u003e User-Agent: curl/7.61.0\n\u003e Accept: */*\n\u003e \n\u003c HTTP/1.1 405 Method Not Allowed\n```\n\nThere are also a few things to remember, like to flush output or close exchange every time we return from the api.\nWhen I used Spring I even did not have to think about it.\n\nTry this part from branch:\n\n```bash\ngit checkout step-3\n```\n\n## Parsing request params\nParsing request params is another \"feature\" which we'll need to implement ourselves in contrary to utilising a framework.\nLet's say we would like our hello api to respond with a name passed as a param, e.g.: \n\n```bash\ncurl localhost:8000/api/hello?name=Marcin\n\nHello Marcin!\n\n```\nWe could parse params with a method like: \n\n```java\npublic static Map\u003cString, List\u003cString\u003e\u003e splitQuery(String query) {\n        if (query == null || \"\".equals(query)) {\n            return Collections.emptyMap();\n        }\n\n        return Pattern.compile(\"\u0026\").splitAsStream(query)\n            .map(s -\u003e Arrays.copyOf(s.split(\"=\"), 2))\n            .collect(groupingBy(s -\u003e decode(s[0]), mapping(s -\u003e decode(s[1]), toList())));\n\n    }\n```\n\nand use it as below: \n\n```java\n Map\u003cString, List\u003cString\u003e\u003e params = splitQuery(exchange.getRequestURI().getRawQuery());\nString noNameText = \"Anonymous\";\nString name = params.getOrDefault(\"name\", List.of(noNameText)).stream().findFirst().orElse(noNameText);\nString respText = String.format(\"Hello %s!\", name);\n           \n```\n\nYou can find complete example in branch:\n\n```bash\ngit checkout step-4\n```\n\nSimilarly if we wanted to use path params, e.g.: \n\n```bash\ncurl localhost:8000/api/items/1\n```\nto get item by id=1, we would need to parse the path ourselves to extract an id from it. This is getting cumbersome.\n\n\n## Secure endpoint\nA common case in each REST API is to protect some endpoints with credentials, e.g. using basic authentication.\nFor each server context we can set an authenticator as below: \n\n```java\nHttpContext context =server.createContext(\"/api/hello\", (exchange -\u003e {\n  // this part remains unchanged\n}));\ncontext.setAuthenticator(new BasicAuthenticator(\"myrealm\") {\n    @Override\n    public boolean checkCredentials(String user, String pwd) {\n        return user.equals(\"admin\") \u0026\u0026 pwd.equals(\"admin\");\n    }\n});\n```\n\nThe \"myrealm\" in `BasicAuthenticator` is a realm name. Realm is a virtual name which can be used to separate different authentication spaces. \nYou can read more about it in [RFC 1945](https://tools.ietf.org/html/rfc1945#section-11)\n\nYou can now invoke this protected endpoint by adding an `Authorization` header like that: \n\n```bash\ncurl -v localhost:8000/api/hello?name=Marcin -H 'Authorization: Basic YWRtaW46YWRtaW4='\n```\n\nThe text after `Basic` is a Base64 encoded `admin:admin`  which are credentials hardcoded in our example code.\nIn real application to authenticate user you would probably get it from the header and compare with username and password store in database.\nIf you skip the header the API will respond with status\n```\nHTTP/1.1 401 Unauthorized\n\n```\n\nCheck out the complete code from branch:\n\n```bash\ngit checkout step-5\n```\n\n## JSON, exception handlers and others\n\nNow it's time for more complex example. \n\nFrom my past experience in software development the most common API I was developing was exchanging JSON.\n\nWe're going to develop an API to register new users. We will use an in-memory database to store them.\n\nOur user domain object will be simple: \n\n```java\n@Value\n@Builder\npublic class User {\n\n    String id;\n    String login;\n    String password;\n}\n\n```\nI'm using Lombok annotations to save me from constructor and getters boilerplate code, it will be generated in build time.\n\nIn REST API I want to pass only login and password so I created a separate domain object: \n\n```java\n@Value\n@Builder\npublic class NewUser {\n\n    String login;\n    String password;\n}\n\n```\n\nUsers will be created in a service which I will use in my API handler. The service method is simply storing the user. \nIn complete application it could do more, like send events after successful user registration.\n\n```java\npublic String create(NewUser user) {\n    return userRepository.create(user);\n}\n```\n\nOur in-memory implementation of repository is as follows: \n```java\n\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport com.consulner.domain.user.NewUser;\nimport com.consulner.domain.user.User;\nimport com.consulner.domain.user.UserRepository;\n\npublic class InMemoryUserRepository implements UserRepository {\n\n    private static final Map USERS_STORE = new ConcurrentHashMap();\n\n    @Override\n    public String create(NewUser newUser) {\n        String id = UUID.randomUUID().toString();\n        User user = User.builder()\n            .id(id)\n            .login(newUser.getLogin())\n            .password(newUser.getPassword())\n            .build();\n        USERS_STORE.put(newUser.getLogin(), user);\n\n        return id;\n    }\n}\n```\nFinally, let's glue all together in handler:\n\n```java\nprotected void handle(HttpExchange exchange) throws IOException {\n        if (!exchange.getRequestMethod().equals(\"POST\")) {\n            throw new UnsupportedOperationException();\n        }\n\n        RegistrationRequest registerRequest = readRequest(exchange.getRequestBody(), RegistrationRequest.class);\n\n        NewUser user = NewUser.builder()\n            .login(registerRequest.getLogin())\n            .password(PasswordEncoder.encode(registerRequest.getPassword()))\n            .build();\n\n        String userId = userService.create(user);\n\n        exchange.getResponseHeaders().set(Constants.CONTENT_TYPE, Constants.APPLICATION_JSON);\n        exchange.sendResponseHeaders(StatusCode.CREATED.getCode(), 0);\n\n        byte[] response = writeResponse(new RegistrationResponse(userId));\n\n        OutputStream responseBody = exchange.getResponseBody();\n        responseBody.write(response);\n        responseBody.close();\n    }\n```\n\nIt translates JSON request into `RegistrationRequest` object: \n\n```java\n@Value\nclass RegistrationRequest {\n\n    String login;\n    String password;\n}\n```\n\nwhich I later map to domain object `NewUser` to finally save it in database and write response as JSON.\n\nI need to translate `RegistrationResponse` object back to JSON string.\n\nMarshalling and unmarshalling JSON is done with Jackson object mapper (`com.fasterxml.jackson.databind.ObjectMapper`).\n\nAnd this is how I instantiate the new handler in application main method: \n\n```java\n public static void main(String[] args) throws IOException {\n        int serverPort = 8000;\n        HttpServer server = HttpServer.create(new InetSocketAddress(serverPort), 0);\n\n        RegistrationHandler registrationHandler = new RegistrationHandler(getUserService(), getObjectMapper(),\n            getErrorHandler());\n        server.createContext(\"/api/users/register\", registrationHandler::handle);\n        \n        // here follows the rest.. \n\n }\n```\n\nYou can find the working example in separate git branch, where I also added a global exception handler which is used\nby the API to respond with a standard JSON error message in case, e.g. when HTTP method is not supported or API request is malformed.\n\n```java\ngit checkout step-6\n```\n\nYou can run the application and try one of the example requests below: \n\n```bash\ncurl -X POST localhost:8000/api/users/register -d '{\"login\": \"test\" , \"password\" : \"test\"}'\n```\n\nresponse: \n```bash\n{\"id\":\"395eab24-1fdd-41ae-b47e-302591e6127e\"}\n```\n\n```bash\ncurl -v -X POST localhost:8000/api/users/register -d '{\"wrong\": \"request\"}'\n```\n\nresponse: \n```bash\n\u003c HTTP/1.1 400 Bad Request\n\u003c Date: Sat, 29 Dec 2018 00:11:21 GMT\n\u003c Transfer-encoding: chunked\n\u003c Content-type: application/json\n\u003c \n* Connection #0 to host localhost left intact\n{\"code\":400,\"message\":\"Unrecognized field \\\"wrong\\\" (class com.consulner.app.api.user.RegistrationRequest), not marked as ignorable (2 known properties: \\\"login\\\", \\\"password\\\"])\\n at [Source: (sun.net.httpserver.FixedLengthInputStream); line: 1, column: 21] (through reference chain: com.consulner.app.api.user.RegistrationRequest[\\\"wrong\\\"])\"}\n```\n\nAlso, by chance I encountered a project [java-express](https://github.com/Simonwep/java-express) \nwhich is a Java counterpart of Node.js [Express](https://expressjs.com/) framework \nand is using jdk.httpserver as well, so all the concepts covered in this article you can find in real-life application framework :) \nwhich is also small enough to digest the codes quickly.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factivestate%2Fjava-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Factivestate%2Fjava-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factivestate%2Fjava-demo/lists"}