but why does this need to be a seperate step? Why not just do it with createWindow() as well? It seems like something you would always do together.
That is because the function addresses need not be constant for all OpenGL contexts but can differ by context, so an OpenGL client application actually needs to resolve the function addresses of OpenGL functions such as glCreateShader or glBindBuffer etc. dynamically at runtime via a platform API.
I still dont get why I have to tell OpenGL to get the adresses of the functions. For instance why don't I have to do that for java if I want to call System.out.println() or any other method provided by java? And if java does that automatically, why doesn't openGL?